django-shop icon indicating copy to clipboard operation
django-shop copied to clipboard

Customizing CustomerForm

Open dismine opened this issue 6 years ago • 25 comments

Hi,

My Customer model has additional fields. And because of that the CustomerForm on checkout stage shows those fields. How can i hide them?

dismine avatar Jun 08 '18 18:06 dismine

You are using the CustomerForm via Cascade, right? Then you have to create you own version of a CustomerForm and register your own implementation of CustomerFormPlugin. There you can refer to your form implementation.

It probably would be easier to allow a specific setting for this. If you want, I can make this overridable via a settings variable, which defaults to the current form.

jrief avatar Jun 08 '18 18:06 jrief

You are using the CustomerForm via Cascade, right?

Correct.

Then you have to create you own version of a CustomerForm and register your own implementation of CustomerFormPlugin. There you can refer to your form implementation.

I also thought about this approach. But problem is that CheckoutSerializer also uses CustomerForm.

It probably would be easier to allow a specific setting for this. If you want, I can make this overridable via a settings variable, which defaults to the current form.

Yes, please, that will make thing much easier.

dismine avatar Jun 08 '18 19:06 dismine

Please retry with HEAD from branch releases/0.12.x. There I added a new configuration directive named SHOP_CASCADE_FORMS. You may override with your own form classes. I'm curios if it works for you. Then I'll add some documentation on it.

jrief avatar Jun 09 '18 20:06 jrief

Hi, unfortunately it doesn't work for some reason. I don't get any error, but also do not see any field.

I basically copied CustomerForm from checkout.py, renamed it and replaced path in settings.

dismine avatar Jun 11 '18 15:06 dismine

@dismine, If it can help you, I just try with this, I see the fields it takes.

in myshop/settings.py:

...
SHOP_CASCADE_FORMS= {
            'CustomerForm': 'myshop.forms.checkout.CustomerForm',
            'GuestForm': 'shop.forms.checkout.GuestForm',
            'ShippingAddressForm': 'shop.forms.checkout.ShippingAddressForm',
            'BillingAddressForm': 'shop.forms.checkout.BillingAddressForm',
            'PaymentMethodForm': 'shop.forms.checkout.PaymentMethodForm',
            'ShippingMethodForm': 'shop.forms.checkout.ShippingMethodForm',
            'ExtraAnnotationForm': 'shop.forms.checkout.ExtraAnnotationForm',
            'AcceptConditionForm': 'shop.forms.checkout.AcceptConditionForm',
        }
...

myshop/forms/checkout.py

# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from django.utils.translation import ugettext_lazy as _

from djng.forms import fields

from shop.models.customer import CustomerModel
from shop.forms.base import DialogModelForm


class CustomerForm(DialogModelForm):
    scope_prefix = 'customer'
    legend = _("Customer's Details")

    email = fields.EmailField(label=_("Email address"))
    first_name = fields.CharField(label=_("First Name"))
    last_name = fields.CharField(label=_("Last Name"))

    class Meta:
        model = CustomerModel
        exclude = ['user', 'recognized', 'number', 'last_access']
        custom_fields = ['email', 'first_name', 'last_name']

    def __init__(self, initial=None, instance=None, *args, **kwargs):
        initial = dict(initial) if initial else {}
        assert instance is not None
        initial.update(dict((f, getattr(instance, f)) for f in self.Meta.custom_fields))
        super(CustomerForm, self).__init__(initial=initial, instance=instance, *args, **kwargs)

    def save(self, commit=True):
        for f in self.Meta.custom_fields:
            setattr(self.instance, f, self.cleaned_data[f])
        return super(CustomerForm, self).save(commit)

    @classmethod
    def form_factory(cls, request, data, cart):
        customer_form = cls(data=data, instance=request.customer)
        if customer_form.is_valid():
            customer_form.instance.recognize_as_registered(request, commit=False)
            customer_form.save()
        return customer_form

haricot avatar Jun 11 '18 16:06 haricot

if it can help you, I just try with this, I see the fields it takes.

Thank you. Yes, i also see fields when my custom form class has name CustomerForm. But why arbitrary name doesn't work? With current approach i cannot use class CustomerForm as a parent.

dismine avatar Jun 11 '18 16:06 dismine

Like this it seems to work:

myshop/forms/checkout.py

# -*- coding: utf-8 -*-
from shop.models.customer import CustomerModel
from shop.forms.checkout import CustomerForm

class CustomerForm( CustomerForm):

    class Meta:
        model = CustomerModel
        exclude = ['user', 'recognized', 'number', 'last_access']
        custom_fields = ['email', 'first_name', 'last_name']

haricot avatar Jun 11 '18 17:06 haricot

Arbitrary name doesn't work because she's coded in hard here, maybe: https://github.com/awesto/django-shop/commit/741146d3d9870dfc7a6bb4642561cbd3531d8634#diff-92de288a3bb676dd22abc143bc151271R31

haricot avatar Jun 11 '18 18:06 haricot

It seems i have found the place that causes the issue:

First of all

class DialogFormPluginBase(ShopPluginBase):
...
def render(self, context, instance, placeholder):
    """
    Return the context to render a DialogFormPlugin
    """
    request = context['request']
    form_data = self.get_form_data(context, instance, placeholder)
    request._plugin_order = getattr(request, '_plugin_order', 0) + 1
    if not isinstance(form_data.get('initial'), dict):
        form_data['initial'] = {}
    form_data['initial'].update(plugin_id=instance.pk, plugin_order=request._plugin_order)
    bound_form = self.get_form_class(instance)(**form_data)
    context[bound_form.form_name] = bound_form
    context['headline_legend'] = bool(instance.glossary.get('headline_legend', True))
    return self.super(DialogFormPluginBase, self).render(context, instance, placeholder)

String context[bound_form.form_name] = bound_form relies on class name. When i change it, i also change variable name in context. Which way to resolve this situation i don't know. For now i just override form_name in my custom form.

dismine avatar Jun 12 '18 09:06 dismine

Hi I think this is the right thread: I would like to extend the RegisterUserForm adding some fields to it, as we would like the customer to put in all her/his information beforehand and then continue in the shop.

I already extended the form RegisterUserForm , adding my fields to it, making it my CustomRegisterUserForm. Then I override the urls of django-SHOP in my urls.py.

But as soon as I call the register-user url, I do not see my additional fields. When debugging using <pre> {% filter force_escape %} {% debug %} {% endfilter %} </pre> in my template, I can see that the form is not my custom form but RegisterUserForm: 'register_user_form': <RegisterUserForm bound=False, valid=False, fields=(email;password1;password2;preset_password)>

I found in shop.cascade.auth the plugin uses AUTH_FORM_TYPES which sets the form back to RegisterUserForm. It would be a nice feature, if this also will be made customizable, so I do not have to extend all the code :-)

markusmo avatar Aug 06 '18 12:08 markusmo

I just successfully overwritten and registered my CustomerForm. Is there any way to also rewrite the static summary? I would like to influence the rendering of this element too, because of the ordering of my fields do not make sense in visual representation (first I have salutation, then email, then name and lastname). Thanks in advance

markusmo avatar Oct 15 '18 10:10 markusmo

There are many static summaries, which one are you referring?

jrief avatar Oct 15 '18 11:10 jrief

Hi I am trying to overwrite the summary of "CustomerForm".


Markus Mohanty MSc. Tel.: 06763239108

Jacob Rief [email protected] schrieb am Mo., 15. Okt. 2018, 13:54:

There are many static summaries, which one are you referring?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/awesto/django-shop/issues/735#issuecomment-429822629, or mute the thread https://github.com/notifications/unsubscribe-auth/ABHi7LhklHdNzD_Cd2SaOpjNJqkyQz6Sks5ulHdlgaJpZM4UgwQL .

markusmo avatar Oct 15 '18 12:10 markusmo

How about reordering the fields in the CustomerForm? Create your own customer form class inheriting from CustomerForm, use field_order to order them and configure this class using the settings directive SHOP_CASCADE_FORMS['CustomerForm'].

The reason I didn't implement a template renderer is, that it would be against the DRY principle, ie. the merchant would have to adopt the customer model, as well as this template. If the above doesn't work, I might rethink about a template approach.

jrief avatar Oct 15 '18 13:10 jrief

I now used field_order and it works fine for the form dialog of CustomerForm, but for static summary it still does not change order of fields.

markusmo avatar Oct 15 '18 14:10 markusmo

I just retested with this setup:

Upgrade to HEAD on branch master

Add to the merchant project:

myshop/forms.py:

from shop.forms.checkout import CustomerForm as CustomerFormBase

class CustomerForm(CustomerFormBase):
    field_order = ['email', 'last_name', 'first_name', 'salutation']

Add to the merchant settings.py

SHOP_CASCADE_FORMS = {
    'CustomerForm': 'myshop.forms.CustomerForm',
}

jrief avatar Oct 16 '18 07:10 jrief

Does this implementation then also change the field_order of the static summary?

markusmo avatar Oct 16 '18 09:10 markusmo

I just made a test with the above, not very realistic example. This is what is rendered inside the customer's summary:

[email protected]
De Master
Adam
Mr.

Note, when rendering the address on the client's envelope, you can use an existing, but overridable template, named shop/templates/shop/address.txt. For static summaries this has not been implemented. If there is a valid use-case for this, I might rethink about that.

jrief avatar Oct 16 '18 10:10 jrief

In my case, my client has requested that Customer is extended so that it represents a company (including VAT-Number, phonenumber and company name). So in the summary it looks quite strange to have it look like this:

Company AG
ATU36196308
+4311234345
Mr.
[email protected]
Firstname
Lastname

It looks like the ordering of the fields of my CustomerModel and then the additional fields of CustomerBase. A more logical ordering would be:

Company AG
ATU36196308
+4311234345
[email protected]
Mr.
Firstname
Lastname

And that's what I would like to achieve.

markusmo avatar Oct 19 '18 08:10 markusmo

I see. That means, that you would like to have the possibility to use a text template in order to render the CustomerForm as summary. Yes, I can implement that, although it means that you implementation gets a little bit less DRY.

jrief avatar Oct 19 '18 09:10 jrief

I would appreciate it. I could not think of any solution, without rewriting the whole form.

markusmo avatar Oct 19 '18 10:10 markusmo

Can i use this feature if i will install package from PyPi?

dismine avatar Oct 26 '18 08:10 dismine

Did you find time, to think of a solution for formatting static summary of forms?

markusmo avatar Oct 29 '18 14:10 markusmo

Yes, this has been implemented now. However it's in a draft-branch which is still under development. Unfortunately not everything in that branch drafts/bootstrap-4-sendcloud is stable and documented yet, hence I didn't release it officially yet. You can either switch to that branch – I can help you on that – or try to back-port that feature into the current stable one.

jrief avatar Nov 15 '18 22:11 jrief

thanks for the info. I will look into it, as soon as I can.

markusmo avatar Nov 16 '18 08:11 markusmo