[gen] Moved Appy fields into appy/fields together with their PX.

This commit is contained in:
Gaetan Delannay 2013-07-08 23:39:16 +02:00
parent 2b5d286668
commit 25b4edfc1d
24 changed files with 3795 additions and 2503 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,463 +0,0 @@
# ------------------------------------------------------------------------------
import types
from appy import Object
from appy.gen import Type
from DateTime import DateTime
from BTrees.IOBTree import IOBTree
from persistent.list import PersistentList
# ------------------------------------------------------------------------------
class Calendar(Type):
'''This field allows to produce an agenda (monthly view) and view/edit
events on it.'''
jsFiles = {'view': ('widgets/calendar.js',)}
def __init__(self, eventTypes, eventNameMethod=None, validator=None,
default=None, show='view', page='main', group=None,
layouts=None, move=0, specificReadPermission=False,
specificWritePermission=False, width=None, height=300,
colspan=1, master=None, masterValue=None, focus=False,
mapping=None, label=None, maxEventLength=50,
otherCalendars=None, additionalInfo=None, startDate=None,
endDate=None, defaultDate=None, preCompute=None,
applicableEvents=None):
Type.__init__(self, validator, (0,1), default, show, page, group,
layouts, move, False, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, True, mapping, label,
None, None, None, None)
# eventTypes can be a "static" list or tuple of strings that identify
# the types of events that are supported by this calendar. It can also
# be a method that computes such a "dynamic" list or tuple. When
# specifying a static list, an i18n label will be generated for every
# event type of the list. When specifying a dynamic list, you must also
# give, in p_eventNameMethod, a method that will accept a single arg
# (=one of the event types from your dynamic list) and return the "name"
# of this event as it must be shown to the user.
self.eventTypes = eventTypes
self.eventNameMethod = eventNameMethod
if (type(eventTypes) == types.FunctionType) and not eventNameMethod:
raise Exception("When param 'eventTypes' is a method, you must " \
"give another method in param 'eventNameMethod'.")
# It is not possible to create events that span more days than
# maxEventLength.
self.maxEventLength = maxEventLength
# When displaying a given month for this agenda, one may want to
# pre-compute, once for the whole month, some information that will then
# be given as arg for other methods specified in subsequent parameters.
# This mechanism exists for performance reasons, to avoid recomputing
# this global information several times. If you specify a method in
# p_preCompute, it will be called every time a given month is shown, and
# will receive 2 args: the first day of the currently shown month (as a
# DateTime instance) and the grid of all shown dates (as a list of lists
# of DateTime instances, one sub-list by row in the month view). This
# grid may hold a little more than dates of the current month.
# Subsequently, the return of your method will be given as arg to other
# methods that you may specify as args of other parameters of this
# Calendar class (see comments below).
self.preCompute = preCompute
# If a method is specified in the following parameters, it must accept
# a single arg (the result of self.preCompute) and must return a list of
# calendars whose events must be shown within this agenda.
# Every element in this list must be a sub-list [object, name, color]
# (not a tuple):
# - object must refer to the other object on which the other calendar
# field is defined;
# - name is the name of the field on this object that stores the
# calendar;
# - color must be a string containing the HTML color (including the
# leading "#" when relevant) into which events of the calendar must
# appear.
self.otherCalendars = otherCalendars
# One may want to add, day by day, custom information in the calendar.
# When a method is given in p_additionalInfo, for every cell of the
# month view, this method will be called with 2 args: the cell's date
# and the result of self.preCompute. The method's result (a string that
# can hold text or a chunk of XHTML) will be inserted in the cell.
self.additionalInfo = additionalInfo
# One may limit event encoding and viewing to some period of time,
# via p_startDate and p_endDate. Those parameters, if given, must hold
# methods accepting no arg and returning a Zope DateTime instance. The
# startDate and endDate will be converted to UTC at 00.00.
self.startDate = startDate
self.endDate = endDate
# If a default date is specified, it must be a method accepting no arg
# and returning a DateTime instance. As soon as the calendar is shown,
# the month where this date is included will be shown. If not default
# date is specified, it will be 'now' at the moment the calendar is
# shown.
self.defaultDate = defaultDate
# For a specific day, all event types may not be applicable. If this is
# the case, one may specify here a method that defines, for a given day,
# a sub-set of all event types. This method must accept 3 args: the day
# in question (as a DateTime instance), the list of all event types,
# which is a copy of the (possibly computed) self.eventTypes) and
# the result of calling self.preCompute. The method must modify
# the 2nd arg and remove from it potentially not applicable events.
# This method can also return a message, that will be shown to the user
# for explaining him why he can, for this day, only create events of a
# sub-set of the possible event types (or even no event at all).
self.applicableEvents = applicableEvents
def getPreComputedInfo(self, obj, monthDayOne, grid):
'''Returns the result of calling self.preComputed, or None if no such
method exists.'''
if self.preCompute:
return self.preCompute(obj.appy(), monthDayOne, grid)
def getSiblingMonth(self, month, prevNext):
'''Gets the next or previous month (depending of p_prevNext) relative
to p_month.'''
dayOne = DateTime('%s/01 UTC' % month)
if prevNext == 'previous':
refDate = dayOne - 1
elif prevNext == 'next':
refDate = dayOne + 33
return refDate.strftime('%Y/%m')
weekDays = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
def getNamesOfDays(self, obj, short=True):
res = []
for day in self.weekDays:
if short:
suffix = '_short'
else:
suffix = ''
res.append(obj.translate('day_%s%s' % (day, suffix)))
return res
def getMonthGrid(self, month):
'''Creates a list of lists of DateTime objects representing the calendar
grid to render for a given p_month.'''
# Month is a string "YYYY/mm".
currentDay = DateTime('%s/01 UTC' % month)
currentMonth = currentDay.month()
res = [[]]
dayOneNb = currentDay.dow() or 7 # This way, Sunday is 7 and not 0.
if dayOneNb != 1:
previousDate = DateTime(currentDay)
# If the 1st day of the month is not a Monday, start the row with
# the last days of the previous month.
for i in range(1, dayOneNb):
previousDate = previousDate - 1
res[0].insert(0, previousDate)
finished = False
while not finished:
# Insert currentDay in the grid
if len(res[-1]) == 7:
# Create a new row
res.append([currentDay])
else:
res[-1].append(currentDay)
currentDay = currentDay + 1
if currentDay.month() != currentMonth:
finished = True
# Complete, if needed, the last row with the first days of the next
# month.
if len(res[-1]) != 7:
while len(res[-1]) != 7:
res[-1].append(currentDay)
currentDay = currentDay + 1
return res
def getOtherCalendars(self, obj, preComputed):
'''Returns the list of other calendars whose events must also be shown
on this calendar.'''
if self.otherCalendars:
res = self.otherCalendars(obj.appy(), preComputed)
# Replace field names with field objects
for i in range(len(res)):
res[i][1] = res[i][0].getField(res[i][1])
return res
def getAdditionalInfoAt(self, obj, date, preComputed):
'''If the user has specified a method in self.additionalInfo, we call
it for displaying this additional info in the calendar, at some
p_date.'''
if not self.additionalInfo: return
return self.additionalInfo(obj.appy(), date, preComputed)
def getEventTypes(self, obj):
'''Returns the (dynamic or static) event types as defined in
self.eventTypes.'''
if type(self.eventTypes) == types.FunctionType:
return self.eventTypes(obj.appy())
else:
return self.eventTypes
def getApplicableEventsTypesAt(self, obj, date, allEventTypes, preComputed,
forBrowser=False):
'''Returns the event types that are applicable at a given p_date. More
precisely, it returns an object with 2 attributes:
* "events" is the list of applicable event types;
* "message", not empty if some event types are not applicable,
contains a message explaining those event types are
not applicable.
'''
if not self.applicableEvents:
eventTypes = allEventTypes
message = None
else:
eventTypes = allEventTypes[:]
message = self.applicableEvents(obj.appy(), date, eventTypes,
preComputed)
res = Object(eventTypes=eventTypes, message=message)
if forBrowser:
res.eventTypes = ','.join(res.eventTypes)
if not res.message:
res.message = ''
else:
res.message = obj.formatText(res.message, format='js')
return res.__dict__
return res
def getEventsAt(self, obj, date, asDict=True):
'''Returns the list of events that exist at some p_date (=day).'''
obj = obj.o # Ensure p_obj is not a wrapper.
if not hasattr(obj.aq_base, self.name): return
years = getattr(obj, self.name)
year = date.year()
if year not in years: return
months = years[year]
month = date.month()
if month not in months: return
days = months[month]
day = date.day()
if day not in days: return
if asDict:
res = [e.__dict__ for e in days[day]]
else:
res = days[day]
return res
def getEventTypeAt(self, obj, date):
'''Returns the event type of the first event defined at p_day, or None
if unspecified.'''
events = self.getEventsAt(obj, date, asDict=False)
if not events: return
return events[0].eventType
def getEventsByType(self, obj, eventType, minDate=None, maxDate=None,
sorted=True, groupSpanned=False):
'''Returns all the events of a given p_eventType. If p_eventType is
None, it returns events of all types. The return value is a list of
2-tuples whose 1st elem is a DateTime instance and whose 2nd elem is
the event.
If p_sorted is True, the list is sorted in chronological order. Else,
the order is random, but the result is computed faster.
If p_minDate and/or p_maxDate is/are specified, it restricts the
search interval accordingly.
If p_groupSpanned is True, events spanned on several days are
grouped into a single event. In this case, tuples in the result
are 3-tuples: (DateTime_startDate, DateTime_endDate, event).
'''
# Prevent wrong combinations of parameters
if groupSpanned and not sorted:
raise Exception('Events must be sorted if you want to get ' \
'spanned events to be grouped.')
obj = obj.o # Ensure p_obj is not a wrapper.
res = []
if not hasattr(obj, self.name): return res
# Compute "min" and "max" tuples
if minDate:
minYear = minDate.year()
minMonth = (minYear, minDate.month())
minDay = (minYear, minDate.month(), minDate.day())
if maxDate:
maxYear = maxDate.year()
maxMonth = (maxYear, maxDate.month())
maxDay = (maxYear, maxDate.month(), maxDate.day())
# Browse years
years = getattr(obj, self.name)
for year in years.keys():
# Don't take this year into account if outside interval
if minDate and (year < minYear): continue
if maxDate and (year > maxYear): continue
months = years[year]
# Browse this year's months
for month in months.keys():
# Don't take this month into account if outside interval
thisMonth = (year, month)
if minDate and (thisMonth < minMonth): continue
if maxDate and (thisMonth > maxMonth): continue
days = months[month]
# Browse this month's days
for day in days.keys():
# Don't take this day into account if outside interval
thisDay = (year, month, day)
if minDate and (thisDay < minDay): continue
if maxDate and (thisDay > maxDay): continue
events = days[day]
# Browse this day's events
for event in events:
# Filter unwanted events
if eventType and (event.eventType != eventType):
continue
# We have found a event.
date = DateTime('%d/%d/%d UTC' % (year, month, day))
if groupSpanned:
singleRes = [date, None, event]
else:
singleRes = (date, event)
res.append(singleRes)
# Sort the result if required
if sorted: res.sort(key=lambda x: x[0])
# Group events spanned on several days if required
if groupSpanned:
# Browse events in reverse order and merge them when appropriate
i = len(res) - 1
while i > 0:
currentDate = res[i][0]
lastDate = res[i][1]
previousDate = res[i-1][0]
currentType = res[i][2].eventType
previousType = res[i-1][2].eventType
if (previousDate == (currentDate-1)) and \
(previousType == currentType):
# A merge is needed
del res[i]
res[i-1][1] = lastDate or currentDate
i -= 1
return res
def hasEventsAt(self, obj, date, otherEvents):
'''Returns True if, at p_date, an event is found of the same type as
p_otherEvents.'''
if not otherEvents: return False
events = self.getEventsAt(obj, date, asDict=False)
if not events: return False
return events[0].eventType == otherEvents[0]['eventType']
def getOtherEventsAt(self, obj, date, otherCalendars):
'''Gets events that are defined in p_otherCalendars at some p_date.'''
res = []
for o, field, color in otherCalendars:
events = field.getEventsAt(o.o, date, asDict=False)
if events:
eventType = events[0].eventType
eventName = field.getEventName(o.o, eventType)
info = Object(name=eventName, color=color)
res.append(info.__dict__)
return res
def getEventName(self, obj, eventType):
'''Gets the name of the event corresponding to p_eventType as it must
appear to the user.'''
if self.eventNameMethod:
return self.eventNameMethod(obj.appy(), eventType)
else:
return obj.translate('%s_event_%s' % (self.labelId, eventType))
def getStartDate(self, obj):
'''Get the start date for this calendar if defined.'''
if self.startDate:
d = self.startDate(obj.appy())
# Return the start date without hour, in UTC.
return DateTime('%d/%d/%d UTC' % (d.year(), d.month(), d.day()))
def getEndDate(self, obj):
'''Get the end date for this calendar if defined.'''
if self.endDate:
d = self.endDate(obj.appy())
# Return the end date without hour, in UTC.
return DateTime('%d/%d/%d UTC' % (d.year(), d.month(), d.day()))
def getDefaultDate(self, obj):
'''Get the default date that must appear as soon as the calendar is
shown.'''
if self.defaultDate:
return self.defaultDate(obj.appy())
else:
return DateTime() # Now
def createEvent(self, obj, date, eventType=None, eventSpan=None,
handleEventSpan=True):
'''Create a new event in the calendar, at some p_date (day).
If p_eventType is given, it is used; else, rq['eventType'] is used.
If p_handleEventSpan is True, we will use p_eventSpan (or
rq["eventSpan"] if p_eventSpan is not given) and also
create the same event for successive days.'''
obj = obj.o # Ensure p_obj is not a wrapper.
rq = obj.REQUEST
# Get values from parameters
if not eventType: eventType = rq['eventType']
if handleEventSpan and not eventSpan:
eventSpan = rq.get('eventSpan', None)
# Split the p_date into separate parts
year, month, day = date.year(), date.month(), date.day()
# Check that the "preferences" dict exists or not.
if not hasattr(obj.aq_base, self.name):
# 1st level: create a IOBTree whose keys are years.
setattr(obj, self.name, IOBTree())
yearsDict = getattr(obj, self.name)
# Get the sub-dict storing months for a given year
if year in yearsDict:
monthsDict = yearsDict[year]
else:
yearsDict[year] = monthsDict = IOBTree()
# Get the sub-dict storing days of a given month
if month in monthsDict:
daysDict = monthsDict[month]
else:
monthsDict[month] = daysDict = IOBTree()
# Get the list of events for a given day
if day in daysDict:
events = daysDict[day]
else:
daysDict[day] = events = PersistentList()
# Create and store the event, excepted if an event already exists.
if not events:
event = Object(eventType=eventType)
events.append(event)
# Span the event on the successive days if required
if handleEventSpan and eventSpan:
nbOfDays = min(int(eventSpan), self.maxEventLength)
for i in range(nbOfDays):
date = date + 1
self.createEvent(obj, date, handleEventSpan=False)
def deleteEvent(self, obj, date, handleEventSpan=True):
'''Deletes an event. It actually deletes all events at p_date.
If p_handleEventSpan is True, we will use rq["deleteNext"] to
delete successive events, too.'''
obj = obj.o # Ensure p_obj is not a wrapper.
if not self.getEventsAt(obj, date): return
daysDict = getattr(obj, self.name)[date.year()][date.month()]
# Remember events, in case we must delete similar ones for next days.
events = self.getEventsAt(obj, date)
del daysDict[date.day()]
rq = obj.REQUEST
if handleEventSpan and rq.has_key('deleteNext') and \
(rq['deleteNext'] == 'True'):
while True:
date = date + 1
if self.hasEventsAt(obj, date, events):
self.deleteEvent(obj, date, handleEventSpan=False)
else:
break
def process(self, obj):
'''Processes an action coming from the calendar widget, ie, the creation
or deletion of a calendar event.'''
rq = obj.REQUEST
action = rq['actionType']
# Get the date for this action
if action == 'createEvent':
return self.createEvent(obj, DateTime(rq['day']))
elif action == 'deleteEvent':
return self.deleteEvent(obj, DateTime(rq['day']))
def getCellStyle(self, obj, date, today):
'''What CSS classes must apply to the table cell representing p_date
in the calendar?'''
res = []
# We must distinguish between past and future dates.
if date < today:
res.append('even')
else:
res.append('odd')
# Week-end days must have a specific style.
if date.aDay() in ('Sat', 'Sun'):
res.append('cellDashed')
return ' '.join(res)
# ------------------------------------------------------------------------------

View file

@ -55,7 +55,7 @@ class ClassDescriptor(Descriptor):
except AttributeError:
attrValue = getattr(self.modelClass, attrName)
hookClass = self.modelClass
if isinstance(attrValue, gen.Type):
if isinstance(attrValue, gen.Field):
if not condition or eval(condition):
attrs.append( (attrName, attrValue, hookClass) )
# Then, add attributes from parent classes
@ -124,7 +124,7 @@ class ClassDescriptor(Descriptor):
attrValue = getattr(self.klass, attrName)
except AttributeError:
attrValue = getattr(self.modelClass, attrName)
if not isinstance(attrValue, gen.Type): continue
if not isinstance(attrValue, gen.Field): continue
FieldDescriptor(attrName, attrValue, self).generate()
def isAbstract(self):

View file

@ -158,7 +158,7 @@ class Generator:
workflow.'''
res = 'none'
for attrValue in klass.__dict__.itervalues():
if isinstance(attrValue, gen.Type):
if isinstance(attrValue, gen.Field):
res = 'class'
elif isinstance(attrValue, gen.State):
res = 'workflow'
@ -218,7 +218,7 @@ class Generator:
# programmatically
moreAttrs = []
for eName, eValue in moduleElem.__dict__.iteritems():
if isinstance(eValue, gen.Type) and (eName not in attrs):
if isinstance(eValue, gen.Field) and (eName not in attrs):
moreAttrs.append(eName)
# Sort them in alphabetical order: else, order would be random
moreAttrs.sort()

View file

@ -385,7 +385,7 @@ class ZopeInstaller:
for baseClass in klass.wrapperClass.__bases__:
if baseClass.__name__ == 'AbstractWrapper': continue
for name, appyType in baseClass.__dict__.iteritems():
if not isinstance(appyType, gen.Type) or \
if not isinstance(appyType, gen.Field) or \
(isinstance(appyType, gen.Ref) and appyType.isBack):
continue # Back refs are initialised within fw refs
appyType.init(name, baseClass, appName)

View file

@ -2,7 +2,7 @@
import os, os.path, sys, re, time, random, types, base64, urllib
from appy import Object
import appy.gen
from appy.gen import Type, Search, Selection, String, Page
from appy.gen import Search, String, Page
from appy.gen.utils import SomeObjects, getClassName, GroupDescr, SearchDescr
from appy.gen.mixins import BaseMixin
from appy.gen.wrappers import AbstractWrapper
@ -476,6 +476,11 @@ class ToolMixin(BaseMixin):
sub-lists of p_sub elements.'''
return splitList(l, sub)
def quote(self, s):
'''Returns the quoted version of p_s.'''
if "'" in s: return '&quot;%s&quot;' % s
return "'%s'" % s
def getLayoutType(self):
'''Guess the current layout type, according to actual URL.'''
url = self.REQUEST['ACTUAL_URL']

View file

@ -1,149 +0,0 @@
# ------------------------------------------------------------------------------
import sha
from appy import Object
from appy.gen import Type
# ------------------------------------------------------------------------------
class OgoneConfig:
'''If you plan, in your app, to perform on-line payments via the Ogone (r)
system, create an instance of this class in your app and place it in the
'ogone' attr of your appy.gen.Config instance.'''
def __init__(self):
# self.env refers to the Ogone environment and can be "test" or "prod".
self.env = 'test'
# You merchant Ogone ID
self.PSPID = None
# Default currency for transactions
self.currency = 'EUR'
# Default language
self.language = 'en_US'
# SHA-IN key (digest will be generated with the SHA-1 algorithm)
self.shaInKey = ''
# SHA-OUT key (digest will be generated with the SHA-1 algorithm)
self.shaOutKey = ''
def __repr__(self): return str(self.__dict__)
# ------------------------------------------------------------------------------
class Ogone(Type):
'''This field allows to perform payments with the Ogone (r) system.'''
urlTypes = ('accept', 'decline', 'exception', 'cancel')
def __init__(self, orderMethod, responseMethod, show='view', page='main',
group=None, layouts=None, move=0, specificReadPermission=False,
specificWritePermission=False, width=None, height=None,
colspan=1, master=None, masterValue=None, focus=False,
mapping=None, label=None):
Type.__init__(self, None, (0,1), None, show, page, group, layouts, move,
False, False,specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, True, mapping, label,
None, None, None, None)
# orderMethod must contain a method returning a dict containing info
# about the order. Following keys are mandatory:
# * orderID An identifier for the order. Don't use the object UID
# for this, use a random number, because if the payment
# is canceled, Ogone will not allow you to reuse the same
# orderID for the next tentative.
# * amount An integer representing the price for this order,
# multiplied by 100 (no floating point value, no commas
# are tolerated. Dont't forget to multiply the amount by
# 100!
self.orderMethod = orderMethod
# responseMethod must contain a method accepting one param, let's call
# it "response". The response method will be called when we will get
# Ogone's response about the status of the payment. Param "response" is
# an object whose attributes correspond to all parameters that you have
# chosen to receive in your Ogone merchant account. After the payment,
# the user will be redirected to the object's view page, excepted if
# your method returns an alternatve URL.
self.responseMethod = responseMethod
noShaInKeys = ('env',)
noShaOutKeys = ('name', 'SHASIGN')
def createShaDigest(self, values, passphrase, keysToIgnore=()):
'''Creates an Ogone-compliant SHA-1 digest based on key-value pairs in
dict p_values and on some p_passphrase.'''
# Create a new dict by removing p_keysToIgnore from p_values, and by
# upperizing all keys.
shaRes = {}
for k, v in values.iteritems():
if k in keysToIgnore: continue
# Ogone: we must not include empty values.
if (v == None) or (v == ''): continue
shaRes[k.upper()] = v
# Create a sorted list of keys
keys = shaRes.keys()
keys.sort()
shaList = []
for k in keys:
shaList.append('%s=%s' % (k, shaRes[k]))
shaObject = sha.new(passphrase.join(shaList) + passphrase)
res = shaObject.hexdigest()
return res
def getValue(self, obj):
'''The "value" of the Ogone field is a dict that collects all the
necessary info for making the payment.'''
tool = obj.getTool()
# Basic Ogone parameters were generated in the app config module.
res = obj.getProductConfig().ogone.copy()
shaKey = res['shaInKey']
# Remove elements from the Ogone config that we must not send in the
# payment request.
del res['shaInKey']
del res['shaOutKey']
res.update(self.callMethod(obj, self.orderMethod))
# Add user-related information
res['CN'] = str(tool.getUserName(normalized=True))
user = obj.appy().appyUser
res['EMAIL'] = user.email or user.login
# Add standard back URLs
siteUrl = tool.getSiteUrl()
res['catalogurl'] = siteUrl
res['homeurl'] = siteUrl
# Add redirect URLs
for t in self.urlTypes:
res['%surl' % t] = '%s/onProcess' % obj.absolute_url()
# Add additional parameter that we want Ogone to give use back in all
# of its responses: the name of this Appy Ogone field. This way, Appy
# will be able to call method m_process below, that will process
# Ogone's response.
res['paramplus'] = 'name=%s' % self.name
# Ensure every value is a str
for k in res.iterkeys():
if not isinstance(res[k], str):
res[k] = str(res[k])
# Compute a SHA-1 key as required by Ogone and add it to the res
res['SHASign'] = self.createShaDigest(res, shaKey,
keysToIgnore=self.noShaInKeys)
return res
def ogoneResponseOk(self, obj):
'''Returns True if the SHA-1 signature from Ogone matches retrieved
params.'''
response = obj.REQUEST.form
shaKey = obj.getProductConfig().ogone['shaOutKey']
digest = self.createShaDigest(response, shaKey,
keysToIgnore=self.noShaOutKeys)
return digest.lower() == response['SHASIGN'].lower()
def process(self, obj):
'''Processes a response from Ogone.'''
# Call the response method defined in this Ogone field.
if not self.ogoneResponseOk(obj):
obj.log('Ogone response SHA failed. REQUEST: %s' % \
str(obj.REQUEST.form))
raise Exception('Failure, possible fraud detection, an ' \
'administrator has been contacted.')
# Create a nice object from the form.
response = Object()
for k, v in obj.REQUEST.form.iteritems():
setattr(response, k, v)
# Call the field method that handles the response received from Ogone.
url = self.responseMethod(obj.appy(), response)
# Redirect the user to the correct page. If the field method returns
# some URL, use it. Else, use the view page of p_obj.
if not url: url = obj.absolute_url()
obj.goto(url)
# ------------------------------------------------------------------------------

View file

@ -1,4 +1,4 @@
<tal:comment replace="nothing">View macro</tal:comment>
<tal:comment replace="nothing">Month view macro</tal:comment>
<div metal:define-macro="viewMonth"
tal:define="fieldName request/fieldName;
ajaxHookId python: contextObj.UID() + fieldName;

View file

@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
import os, os.path, mimetypes
import appy.pod
from appy.gen import Type, Search, Ref, String, WorkflowAnonymous
from appy.gen import Field, Search, Ref, String, WorkflowAnonymous
from appy.gen.indexer import defaultIndexes
from appy.gen.utils import createObject
from appy.px import Px
@ -370,7 +370,7 @@ class AbstractWrapper(object):
appUrl=app.absolute_url(); appFolder=app.data;
appName=ztool.getAppName(); _=ztool.translate;
req=ztool.REQUEST; resp=req.RESPONSE;
lang=ztool.getUserLanguage();
lang=ztool.getUserLanguage(); q=ztool.quote;
layoutType=ztool.getLayoutType();
contextObj=ztool.getPublishedObject(layoutType) or \
ztool.getHomeObject();
@ -998,7 +998,7 @@ class AbstractWrapper(object):
# Now, let's try to return a real attribute.
res = object.__getattribute__(self, name)
# If we got an Appy type, return the value of this type for this object
if isinstance(res, Type):
if isinstance(res, Field):
o = self.o
if isinstance(res, Ref):
return res.getValue(o, noListIfSingleObj=True)