"""Fields are used to control what's displayed in forms. It makes the linkbetween something to edit and its display in the form. Actual display is handledby a widget associated to the field.:organization: Logilab:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__docformat__="restructuredtext en"fromwarningsimportwarnfromdatetimeimportdatetimefromlogilab.mtconverterimportxml_escapefromlogilab.common.decoratorsimportcachedfromyams.schemaimportKNOWN_METAATTRIBUTESfromyams.constraintsimport(SizeConstraint,StaticVocabularyConstraint,FormatConstraint)fromcubicwebimportBinary,tags,uilib,utilsfromcubicweb.webimportINTERNAL_FIELD_VALUE,ProcessFormError,eid_param, \formwidgetsasfwclassUnmodifiedField(Exception):"""raise this when a field has not actually been edited and you want to skip it """defvocab_sort(vocab):"""sort vocabulary, considering option groups"""result=[]partresult=[]forlabel,valueinvocab:ifvalueisNone:# opt group startifpartresult:result+=sorted(partresult)partresult=[]result.append((label,value))else:partresult.append((label,value))result+=sorted(partresult)returnresult_MARKER=object()classField(object):"""This class is the abstract base class for all fields. It hold a bunch of attributes which may be used for fine control of the behaviour of a concret field. All the attributes described below have sensible default value which may be overriden by value given to field's constructor. :name: name of the field (basestring), should be unique in a form. :id: dom identifier (default to the same value as `name`), should be unique in a form. :label: label of the field (default to the same value as `name`). :help: help message about this field. :widget: widget associated to the field. Each field class has a default widget class which may be overriden per instance. :required: bool flag telling if the field is required or not. :value: field's value, used when no value specified by other means. XXX explain :choices: static vocabulary for this field. May be a list of values or a list of (label, value) tuples if specified. :sort: bool flag telling if the vocabulary (either static vocabulary specified in `choices` or dynamic vocabulary fetched from the form) should be sorted on label. :internationalizable: bool flag telling if the vocabulary labels should be translated using the current request language. :eidparam: bool flag telling if this field is linked to a specific entity :role: when the field is linked to an entity attribute or relation, tells the role of the entity in the relation (eg 'subject' or 'object') :fieldset: optional fieldset to which this field belongs to :order: key used by automatic forms to sort fields """# default widget associated to this class of fields. May be overriden per# instancewidget=fw.TextInput# does this field requires a multipart formneeds_multipart=False# class attribute used for ordering of fields in a form__creation_rank=0eidparam=Falserole=Noneid=Nonehelp=Nonerequired=Falsechoices=Nonesort=Trueinternationalizable=Falsefieldset=Noneorder=Nonevalue=_MARKERdef__init__(self,name=None,label=_MARKER,widget=None,**kwargs):forkey,valinkwargs.items():ifkey=='initial':warn('[3.6] use value instead of initial',DeprecationWarning,stacklevel=3)key='value'asserthasattr(self.__class__,key)andnotkey[0]=='_',keysetattr(self,key,val)self.name=nameiflabelis_MARKER:label=nameor_MARKERself.label=label# has to be done after other attributes initializationself.init_widget(widget)# ordering number for this field instanceself.creation_rank=Field.__creation_rankField.__creation_rank+=1def__unicode__(self):returnu'<%s name=%r eidparam=%s role=%r id=%r value=%r visible=%r @%x>'%(self.__class__.__name__,self.name,self.eidparam,self.role,self.id,self.value,self.is_visible(),id(self))def__repr__(self):returnself.__unicode__().encode('utf-8')definit_widget(self,widget):ifwidgetisnotNone:self.widget=widgetelifself.choicesandnotself.widget.vocabulary_widget:self.widget=fw.Select()ifisinstance(self.widget,type):self.widget=self.widget()defset_name(self,name):"""automatically set .label when name is set"""assertnameself.name=nameifself.labelis_MARKER:self.label=namedefis_visible(self):"""return true if the field is not an hidden field"""returnnotisinstance(self.widget,fw.HiddenInput)defactual_fields(self,form):"""return actual fields composing this field in case of a compound field, usually simply return self """yieldselfdefformat_value(self,req,value):"""return value suitable for display where value may be a list or tuple of values """ifisinstance(value,(list,tuple)):return[self.format_single_value(req,val)forvalinvalue]returnself.format_single_value(req,value)defformat_single_value(self,req,value):"""return value suitable for display"""ifvalueisNoneorvalueisFalse:returnu''ifvalueisTrue:returnu'1'returnunicode(value)defget_widget(self,form):"""return the widget instance associated to this field"""returnself.widget# cached is necessary else we get some pb on entity creation : entity.eid is# modified from creation mark (eg 'X') to its actual eid (eg 123), and then# `field.input_name()` won't return the right key anymore if not cached# (first call to input_name done *before* eventual eid affectation).@cacheddefinput_name(self,form,suffix=None):"""return 'qualified name' for this field"""name=self.role_name()ifsuffixisnotNone:name+=suffixifself.eidparam:returneid_param(name,form.edited_entity.eid)returnnamedefrole_name(self):"""return <field.name>-<field.role> if role is specified, else field.name"""ifself.roleisnotNone:return'%s-%s'%(self.name,self.role)returnself.namedefdom_id(self,form,suffix=None):"""return an html dom identifier for this field"""id=self.idorself.role_name()ifsuffixisnotNone:id+=suffixifself.eidparam:returneid_param(id,form.edited_entity.eid)returniddeftyped_value(self,form,load_bytes=False):ifself.eidparamandself.roleisnotNone:entity=form.edited_entityifform._cw.vreg.schema.rschema(self.name).final:ifentity.has_eid()orself.nameinentity:returngetattr(entity,self.name)elifentity.has_eid()orentity.relation_cached(self.name,self.role):return[r[0]forrinentity.related(self.name,self.role)]returnself.initial_typed_value(form,load_bytes)definitial_typed_value(self,form,load_bytes):ifself.valueisnot_MARKER:ifcallable(self.value):returnself.value(form)returnself.valueformattr='%s_%s_default'%(self.role,self.name)ifhasattr(form,formattr):warn('[3.6] %s.%s deprecated, use field.value'%(form.__class__.__name__,formattr),DeprecationWarning)returngetattr(form,formattr)()ifself.eidparamandself.roleisnotNone:ifform._cw.vreg.schema.rschema(self.name).final:returnform.edited_entity.e_schema.default(self.name)return()returnNonedefexample_format(self,req):"""return a sample string describing what can be given as input for this field """returnu''defrender(self,form,renderer):"""render this field, which is part of form, using the given form renderer """widget=self.get_widget(form)returnwidget.render(form,self,renderer)defvocabulary(self,form,**kwargs):"""return vocabulary for this field. This method will be called by widgets which requires a vocabulary. """assertself.choicesisnotNoneifcallable(self.choices):try:ifgetattr(self.choices,'im_self',None)isself:vocab=self.choices(form=form,**kwargs)else:vocab=self.choices(form=form,field=self,**kwargs)exceptTypeError:warn('[3.6] %s: choices should now take ''the form and field as named arguments'%self,DeprecationWarning)try:vocab=self.choices(form=form,**kwargs)exceptTypeError:warn('[3.3] %s: choices should now take ''the form and field as named arguments'%self,DeprecationWarning)vocab=self.choices(req=form._cw,**kwargs)else:vocab=self.choicesifvocabandnotisinstance(vocab[0],(list,tuple)):vocab=[(x,x)forxinvocab]ifself.internationalizable:# the short-cirtcuit 'and' boolean operator is used here to permit# a valid empty string in vocabulary without attempting to translate# it by gettext (which can lead to weird strings display)vocab=[(labelandform._cw._(label),value)forlabel,valueinvocab]ifself.sort:vocab=vocab_sort(vocab)returnvocabdefformat(self,form):"""return MIME type used for the given (text or bytes) field"""ifself.eidparamandself.role=='subject':entity=form.edited_entityifentity.e_schema.has_metadata(self.name,'format')and(entity.has_eid()or'%s_format'%self.nameinentity):returnform.edited_entity.attr_metadata(self.name,'format')returnform._cw.property_value('ui.default-text-format')defencoding(self,form):"""return encoding used for the given (text) field"""ifself.eidparam:entity=form.edited_entityifentity.e_schema.has_metadata(self.name,'encoding')and(entity.has_eid()or'%s_encoding'%self.nameinentity):returnform.edited_entity.attr_metadata(self.name,'encoding')returnform._cw.encodingdefform_init(self,form):"""method called before by build_context to trigger potential field initialization requiring the form instance """passdefhas_been_modified(self,form):ifself.is_visible():# fields not corresponding to an entity attribute / relations# are considered modifiedifnotself.eidparamornotself.roleornotform.edited_entity.has_eid():returnTrue# XXXtry:ifself.role=='subject':previous_value=getattr(form.edited_entity,self.name)else:previous_value=getattr(form.edited_entity,'reverse_%s'%self.name)exceptAttributeError:# fields with eidparam=True but not corresponding to an actual# attribute or relationreturnTrue# if it's a non final relation, we need the eidsifisinstance(previous_value,tuple):# widget should return a set of untyped eidsprevious_value=set(unicode(e.eid)foreinprevious_value)try:new_value=self.process_form_value(form)exceptProcessFormError:returnTrueexceptUnmodifiedField:returnFalseifprevious_value==new_value:returnFalse# not modifiedreturnTruereturnFalsedefprocess_form_value(self,form):"""process posted form and return correctly typed value"""try:returnform.formvalues[self]exceptKeyError:value=form.formvalues[self]=self._process_form_value(form)returnvaluedef_process_form_value(self,form):widget=self.get_widget(form)value=widget.process_field_data(form,self)returnself._ensure_correctly_typed(form,value)def_ensure_correctly_typed(self,form,value):"""widget might to return date as a correctly formatted string or as correctly typed objects, but process_for_value must return a typed value. Override this method to type the value if necessary """returnvalueorNonedefprocess_posted(self,form):forfieldinself.actual_fields(form):iffieldisself:try:yieldfield,field.process_form_value(form)exceptUnmodifiedField:continueelse:# recursive function: we might have compound fields# of compound fields (of compound fields of ...)forfield,valueinfield.process_posted(form):yieldfield,valueclassStringField(Field):widget=fw.TextAreasize=45def__init__(self,name=None,max_length=None,**kwargs):self.max_length=max_length# must be set before super callsuper(StringField,self).__init__(name=name,**kwargs)definit_widget(self,widget):ifwidgetisNone:ifself.choices:widget=fw.Select()elifself.max_lengthandself.max_length<257:widget=fw.TextInput()super(StringField,self).init_widget(widget)ifisinstance(self.widget,fw.TextArea):self.init_text_area(self.widget)elifisinstance(self.widget,fw.TextInput):self.init_text_input(self.widget)definit_text_input(self,widget):ifself.max_length:widget.attrs.setdefault('size',min(self.size,self.max_length))widget.attrs.setdefault('maxlength',self.max_length)definit_text_area(self,widget):ifself.max_length<513:widget.attrs.setdefault('cols',60)widget.attrs.setdefault('rows',5)classPasswordField(StringField):widget=fw.PasswordInputdeftyped_value(self,form,load_bytes=False):ifself.eidparam:# no way to fetch actual password value with cwifform.edited_entity.has_eid():returnINTERNAL_FIELD_VALUEreturnself.initial_typed_value(form,load_bytes)returnsuper(PasswordField,self).typed_value(form,load_bytes)classRichTextField(StringField):widget=Nonedef__init__(self,format_field=None,**kwargs):super(RichTextField,self).__init__(**kwargs)self.format_field=format_fielddefinit_text_area(self,widget):passdefget_widget(self,form):ifself.widgetisNone:ifself.use_fckeditor(form):returnfw.FCKEditor()widget=fw.TextArea()self.init_text_area(widget)returnwidgetreturnself.widgetdefget_format_field(self,form):ifself.format_field:returnself.format_field# we have to cache generated field since it's use as key in the# context dictionnaryreq=form._cwtry:returnreq.data[self]exceptKeyError:fkwargs={'eidparam':self.eidparam,'role':self.role}ifself.use_fckeditor(form):# if fckeditor is used and format field isn't explicitly# deactivated, we want an hidden field for the formatfkwargs['widget']=fw.HiddenInput()fkwargs['value']='text/html'else:# else we want a format selectorfkwargs['widget']=fw.Select()fcstr=FormatConstraint()fkwargs['choices']=fcstr.vocabulary(form=form)fkwargs['internationalizable']=Truefkwargs['value']=self.formatfkwargs['eidparam']=self.eidparamfield=StringField(name=self.name+'_format',**fkwargs)req.data[self]=fieldreturnfielddefactual_fields(self,form):yieldselfformat_field=self.get_format_field(form)ifformat_field:yieldformat_fielddefuse_fckeditor(self,form):"""return True if fckeditor should be used to edit entity's attribute named `attr`, according to user preferences """ifform._cw.use_fckeditor():returnself.format(form)=='text/html'returnFalsedefrender(self,form,renderer):format_field=self.get_format_field(form)ifformat_field:# XXX we want both fields to remain vertically alignedifformat_field.is_visible():format_field.widget.attrs['style']='display: block'result=format_field.render(form,renderer)else:result=u''returnresult+self.get_widget(form).render(form,self,renderer)classFileField(StringField):widget=fw.FileInputneeds_multipart=Truedef__init__(self,format_field=None,encoding_field=None,name_field=None,**kwargs):super(FileField,self).__init__(**kwargs)self.format_field=format_fieldself.encoding_field=encoding_fieldself.name_field=name_fielddefactual_fields(self,form):yieldselfifself.format_field:yieldself.format_fieldifself.encoding_field:yieldself.encoding_fieldifself.name_field:yieldself.name_fielddeftyped_value(self,form,load_bytes=False):ifself.eidparamandself.roleisnotNone:ifform.edited_entity.has_eid():ifload_bytes:returngetattr(form.edited_entity,self.name)# don't actually load data# XXX value should reflect if some file is already attached# * try to display name metadata# * check length(data) / data != nullreturnTruereturnFalsereturnsuper(FileField,self).typed_value(form,load_bytes)defrender(self,form,renderer):wdgs=[self.get_widget(form).render(form,self,renderer)]ifself.format_fieldorself.encoding_field:divid='%s-advanced'%self.input_name(form)wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>'%(xml_escape(uilib.toggle_action(divid)),form._cw._('show advanced fields'),xml_escape(form._cw.build_url('data/puce_down.png')),form._cw._('show advanced fields')))wdgs.append(u'<div id="%s" class="hidden">'%divid)ifself.name_field:wdgs.append(self.render_subfield(form,self.name_field,renderer))ifself.format_field:wdgs.append(self.render_subfield(form,self.format_field,renderer))ifself.encoding_field:wdgs.append(self.render_subfield(form,self.encoding_field,renderer))wdgs.append(u'</div>')ifnotself.requiredandself.typed_value(form):# trick to be able to delete an uploaded filewdgs.append(u'<br/>')wdgs.append(tags.input(name=self.input_name(form,u'__detach'),type=u'checkbox'))wdgs.append(form._cw._('detach attached file'))returnu'\n'.join(wdgs)defrender_subfield(self,form,field,renderer):return(renderer.render_label(form,field)+field.render(form,renderer)+renderer.render_help(form,field)+u'<br/>')def_process_form_value(self,form):posted=form._cw.formifself.input_name(form,u'__detach')inposted:# drop current file value on explictily asked to detachreturnNonetry:value=posted[self.input_name(form)]exceptKeyError:# raise UnmodifiedField instead of returning None, since the later# will try to remove already attached file if anyraiseUnmodifiedField()# skip browser submitted mime typefilename,_,stream=value# value is a 3-uple (filename, mimetype, stream)value=Binary(stream.read())ifnotvalue.getvalue():# usually an unexistant filevalue=Noneelse:# set filename on the Binary instance, may be used later in hooksvalue.filename=filenamereturnvalueclassEditableFileField(FileField):editable_formats=('text/plain','text/html','text/rest')defrender(self,form,renderer):wdgs=[super(EditableFileField,self).render(form,renderer)]ifself.format(form)inself.editable_formats:data=self.typed_value(form,load_bytes=True)ifdata:encoding=self.encoding(form)try:form.formvalues[self]=unicode(data.getvalue(),encoding)exceptUnicodeError:passelse:ifnotself.required:msg=form._cw._('You can either submit a new file using the browse button above'', or choose to remove already uploaded file by checking the ''"detach attached file" check-box, or edit file content online ''with the widget below.')else:msg=form._cw._('You can either submit a new file using the browse button above'', or edit file content online with the widget below.')wdgs.append(u'<p><b>%s</b></p>'%msg)wdgs.append(fw.TextArea(setdomid=False).render(form,self,renderer))# XXX restore form context?return'\n'.join(wdgs)def_process_form_value(self,form):value=form._cw.form.get(self.input_name(form))ifisinstance(value,unicode):# file modified using a text widgetreturnBinary(value.encode(self.encoding(form)))returnsuper(EditableFileField,self)._process_form_value(form)classIntField(Field):def__init__(self,min=None,max=None,**kwargs):super(IntField,self).__init__(**kwargs)self.min=minself.max=maxifisinstance(self.widget,fw.TextInput):self.widget.attrs.setdefault('size',5)self.widget.attrs.setdefault('maxlength',15)def_ensure_correctly_typed(self,form,value):ifisinstance(value,basestring):try:returnint(value)exceptValueError:raiseProcessFormError(form._cw._('an integer is expected'))returnvalueclassBooleanField(Field):widget=fw.Radiodefvocabulary(self,form):ifself.choices:returnsuper(BooleanField,self).vocabulary(form)return[(form._cw._('yes'),'1'),(form._cw._('no'),'')]def_ensure_correctly_typed(self,form,value):returnbool(value)classFloatField(IntField):defformat_single_value(self,req,value):formatstr=req.property_value('ui.float-format')ifvalueisNone:returnu''returnformatstr%float(value)defrender_example(self,req):returnself.format_single_value(req,1.234)def_ensure_correctly_typed(self,form,value):ifisinstance(value,basestring):try:returnfloat(value)exceptValueError:raiseProcessFormError(form._cw._('a float is expected'))returnNoneclassDateField(StringField):widget=fw.JQueryDatePickerformat_prop='ui.date-format'etype='Date'defformat_single_value(self,req,value):ifvalue:returnutils.ustrftime(value,req.property_value(self.format_prop))returnu''defrender_example(self,req):returnself.format_single_value(req,datetime.now())def_ensure_correctly_typed(self,form,value):ifisinstance(value,basestring):try:value=form._cw.parse_datetime(value,self.etype)exceptValueError,ex:raiseProcessFormError(unicode(ex))returnvalueclassDateTimeField(DateField):widget=fw.JQueryDateTimePickerformat_prop='ui.datetime-format'etype='Datetime'classTimeField(DateField):widget=fw.JQueryTimePickerformat_prop='ui.time-format'etype='Time'# relation vocabulary helper functions #########################################defrelvoc_linkedto(entity,rtype,role):# first see if its specified by __linkto form parameterslinkedto=entity.linked_to(rtype,role)iflinkedto:buildent=entity._cw.entity_from_eidreturn[(buildent(eid).view('combobox'),eid)foreidinlinkedto]return[]defrelvoc_init(entity,rtype,role,required=False):# it isn't, check if the entity provides a method to get correct valuesvocab=[]ifnotrequired:vocab.append(('',INTERNAL_FIELD_VALUE))# vocabulary doesn't include current values, add themifentity.has_eid():rset=entity.related(rtype,role)vocab+=[(e.view('combobox'),e.eid)foreinrset.entities()]returnvocabdefrelvoc_unrelated(entity,rtype,role,limit=None):ifisinstance(rtype,basestring):rtype=entity._cw.vreg.schema.rschema(rtype)ifentity.has_eid():done=set(row[0]forrowinentity.related(rtype,role))else:done=Noneresult=[]rsetsize=Noneforobjtypeinrtype.targets(entity.e_schema,role):iflimitisnotNone:rsetsize=limit-len(result)result+=_relvoc_unrelated(entity,rtype,objtype,role,rsetsize,done)iflimitisnotNoneandlen(result)>=limit:breakreturnresultdef_relvoc_unrelated(entity,rtype,targettype,role,limit,done):"""return unrelated entities for a given relation and target entity type for use in vocabulary """ifdoneisNone:done=set()res=[]forentityinentity.unrelated(rtype,targettype,role,limit).entities():ifentity.eidindone:continuedone.add(entity.eid)res.append((entity.view('combobox'),entity.eid))returnresclassRelationField(Field):"""the relation field to edit non final relations of an entity"""@staticmethoddeffromcardinality(card,**kwargs):kwargs.setdefault('widget',fw.Select(multiple=cardin'*+'))returnRelationField(**kwargs)defchoices(self,form,limit=None):"""Take care, choices function for relation field instance should take an extra 'limit' argument, with default to None. This argument is used by the 'unrelateddivs' view (see in autoform) and when it's specified (eg not None), vocabulary returned should: * not include already related entities * have a max size of `limit` entities """entity=form.edited_entity# first see if its specified by __linkto form parametersiflimitisNone:linkedto=relvoc_linkedto(entity,self.name,self.role)iflinkedto:returnlinkedtovocab=relvoc_init(entity,self.name,self.role,self.required)else:vocab=[]# it isn't, check if the entity provides a method to get correct valuesmethod='%s_%s_vocabulary'%(self.role,self.name)try:vocab+=getattr(form,method)(self.name,limit)warn('[3.6] found %s on %s, should override field.choices instead (need tweaks)'%(method,form),DeprecationWarning)exceptAttributeError:vocab+=relvoc_unrelated(entity,self.name,self.role,limit)ifself.sort:vocab=vocab_sort(vocab)returnvocabdefform_init(self,form):#if not self.display_value(form):value=form.edited_entity.linked_to(self.name,self.role)ifvalue:searchedvalues=['%s:%s:%s'%(self.name,eid,self.role)foreidinvalue]# remove associated __linkto hidden fieldsforfieldinform.root_form.fields_by_name('__linkto'):iffield.valueinsearchedvalues:form.root_form.remove_field(field)form.formvalues[self]=valuedefformat_single_value(self,req,value):returnvaluedef_process_form_value(self,form):"""process posted form and return correctly typed value"""widget=self.get_widget(form)values=widget.process_field_data(form,self)ifvaluesisNone:values=()elifnotisinstance(values,list):values=(values,)eids=set()foreidinvalues:# XXX 'not eid' for AutoCompletionWidget, deal with this in the widgetifnoteidoreid==INTERNAL_FIELD_VALUE:continuetyped_eid=form.actual_eid(eid)iftyped_eidisNone:form._cw.data['pendingfields'].add((form,self))returnNoneeids.add(typed_eid)returneidsclassCompoundField(Field):def__init__(self,fields,*args,**kwargs):super(CompoundField,self).__init__(*args,**kwargs)self.fields=fieldsdefsubfields(self,form):returnself.fieldsdefactual_fields(self,form):return[self]+list(self.fields)defguess_field(eschema,rschema,role='subject',skip_meta_attr=True,**kwargs):"""return the most adapated widget to edit the relation 'subjschema rschema objschema' according to information found in the schema """fieldclass=Nonerdef=eschema.rdef(rschema,role)ifrole=='subject':targetschema=rdef.objectifrschema.final:ifrdef.get('internationalizable'):kwargs.setdefault('internationalizable',True)else:targetschema=rdef.subjectcard=rdef.role_cardinality(role)kwargs['required']=cardin'1+'kwargs['name']=rschema.typekwargs['role']=roleifrole=='object':kwargs.setdefault('label',(eschema.type,rschema.type+'_object'))else:kwargs.setdefault('label',(eschema.type,rschema.type))kwargs['eidparam']=Truekwargs.setdefault('help',rdef.description)ifrschema.final:ifskip_meta_attrandrschemaineschema.meta_attributes():returnNonefieldclass=FIELDS[targetschema]iffieldclassisStringField:ifeschema.has_metadata(rschema,'format'):# use RichTextField instead of StringField if the attribute has# a "format" metadata. But getting information from constraints# may be useful anyway...forcstrinrdef.constraints:ifisinstance(cstr,StaticVocabularyConstraint):raiseException('rich text field with static vocabulary')returnRichTextField(**kwargs)# init StringField parameters according to constraintsforcstrinrdef.constraints:ifisinstance(cstr,StaticVocabularyConstraint):kwargs.setdefault('choices',cstr.vocabulary)breakforcstrinrdef.constraints:ifisinstance(cstr,SizeConstraint)andcstr.maxisnotNone:kwargs['max_length']=cstr.maxreturnStringField(**kwargs)iffieldclassisFileField:formetadatainKNOWN_METAATTRIBUTES:metaschema=eschema.has_metadata(rschema,metadata)ifmetaschemaisnotNone:kwargs['%s_field'%metadata]=guess_field(eschema,metaschema,skip_meta_attr=False)returnfieldclass(**kwargs)returnRelationField.fromcardinality(card,**kwargs)FIELDS={'Boolean':BooleanField,'Bytes':FileField,'Date':DateField,'Datetime':DateTimeField,'Int':IntField,'Float':FloatField,'Decimal':StringField,'Password':PasswordField,'String':StringField,'Time':TimeField,}