zopefoundation / z3c.form

An advanced form and widget framework for Zope 3
Other
8 stars 39 forks source link

defaultFactory are not handled via applyChanges in zc3.form.form.py #32

Open loechel opened 8 years ago

loechel commented 8 years ago

https://github.com/zopefoundation/z3c.form/blob/master/src/z3c/form/form.py#L46 tries to apply Changes on data if the have Changed, If you use a defaultFactory on a zope.schema field the value did not change, but also did not get written. This results in a side effect that if you try to resolve field value in a browser view the defaultFactory is executed again, and could provide a different result to different users.

easy solution always write defaultFactory Fields: change

if util.changedField(field.field, newValue, context=content):

to

if util.changedField(field.field, newValue, context=content) or field.field.defaultFactory:
projekt01 commented 8 years ago

I do not understand your question. What do you mean with: "if you try to resolve field value in a browser view the defaultFactory is executed again"

What do you mean with resolve field value?

loechel commented 8 years ago

I am sorry if my english is not good enough to explain the issue.

the value of a default factory is never written to the persitence layer, so browser views recalculate the expected value from the default factory.

Example: User 1 has name foo which is provided to the form via a default factory. User 1 submit form, which is written into db

User 2 access a browser view of the data. As name was not written, the default factory recaluclates data, which if for the current user 2 bar.

so value should be foo for everybody, on every view. But it gets different values on every access.

projekt01 commented 8 years ago

The default value concept should work in z3c.form. What you are probably seeing could depend on the default attribute in zope.schema field. The z3c.schema field defines a missing_value and a default attribute. The z3c.form should be able to compare "field.default is not field.missing_value" and then use the default value if the given input is NO_VALUE. But this does not fit for dynamic values.

You can try to define a factory for the field.default aka defaultFactory. Something like:

def getRequest():
    interaction = zope.security.management.getInteraction()
    return interaction.participations[0]

def defaultUsernameFactory():
    request = getRequest()
    return request.annotations.get('username', '')

Then you can define a schema with a field like:

class IUser(zope.interface.Interface):
    username= zope.schemaChoice(
        title=u"Country"
        defaultFactory=defaultUsernameFactory
        )

Anf your form could look like:

class UserForm(Form):

    fields = fieldFields(interfaces.IUser).select('username')

    def update(self):
        # setup username
        self.request.annotations('username') = 'Fred'
        super(UserForm, self).update()

Does this make sense?

idgserpro commented 5 years ago

Another consequence of this is when we try to use the current date as default value of a date field:

def current_date():
    return datetime.date.today()

class IContent(model.Schema):
    date = schema.Date(
        title=_(u'Date'),
        required=True,
        defaultFactory=current_date,
    )

If I create content on 12/10/2018 and then try to access the date field on 12/11/2018, the returned value will be 12/11/2018 (current date) and not 12/10/2018. That is, it calls the current_date method again.

idgserpro commented 5 years ago

@projekt01 your proposal of https://github.com/zopefoundation/z3c.form/issues/32#issuecomment-152394631 doesn't solve the problem of https://github.com/zopefoundation/z3c.form/issues/32#issuecomment-445899970. @loechel's proposal makes more sense.