Repository: saleor/saleor

Issue:
Simplify address form for better UX
Suggestions:
- one field for name
- one field for address, only zip-code in separate field (http://uxdesign.smashingmagazine.com/wp-content/uploads/2013/03/good-vs-bad-22-500.jpg)
- non-required fields in separate, expandable section, hidden by default



Discussion:
You forgot to include any rationale.

Ticket edited.

What about company name? Do we need this field? Or name will contains it as well?

Since we do not pass these anywhere outside of the system I would be very happy with one field for postal address (with separate postal code and country as these can affect shipping), one field for name.

As for names:
- Mr. Henry M. Paulson Jr
- Her Majesty the Queen
- Rosa María Pérez Martínez de De la Cruz
- 押井守 (Oshii Mamoru, last name goes first in many Asian cultures)
- 毛泽东 (Mao Tse-tung, last name is first, informal addressing must include both names, you cannot say “Hi, Tse-tung”)


Resolving patch:
diff --git a/saleor/order/views.py b/saleor/order/views.py
--- a/saleor/order/views.py
+++ b/saleor/order/views.py
@@ -55,14 +55,10 @@ def start_payment(request, order, variant):
     defaults = {'total': total.gross,
                 'tax': total.tax, 'currency': total.currency,
                 'delivery': order.get_delivery_total().gross,
-                'billing_first_name': billing.first_name,
-                'billing_last_name': billing.last_name,
-                'billing_address_1': billing.street_address_1,
-                'billing_address_2': billing.street_address_2,
+                'billing_address_1': billing.street_address,
                 'billing_city': billing.city,
                 'billing_postcode': billing.postal_code,
-                'billing_country_code': billing.country,
-                'billing_country_area': billing.country_area}
+                'billing_country_code': billing.country}
     if not variant in [v for v, n in settings.CHECKOUT_PAYMENT_CHOICES]:
         raise Http404('%r is not a valid payment variant' % (variant,))
     with transaction.atomic():
diff --git a/saleor/userprofile/models.py b/saleor/userprofile/models.py
--- a/saleor/userprofile/models.py
+++ b/saleor/userprofile/models.py
@@ -80,33 +80,17 @@ def are_identical(self, addr1, addr2):
 @python_2_unicode_compatible
 class Address(models.Model):
 
-    first_name = models.CharField(
-        pgettext_lazy('Address field', 'first name'),
-        max_length=256, blank=True)
-    last_name = models.CharField(
-        pgettext_lazy('Address field', 'last name'),
-        max_length=256, blank=True)
-    company_name = models.CharField(
-        pgettext_lazy('Address field', 'company name'),
-        max_length=256, blank=True)
-    street_address_1 = models.CharField(
-        pgettext_lazy('Address field', 'street address 1'),
-        max_length=256)
-    street_address_2 = models.CharField(
-        pgettext_lazy('Address field', 'street address 2'),
-        max_length=256, blank=True)
+    name = models.CharField(
+        pgettext_lazy('Address field', 'name'), max_length=256)
     city = models.CharField(
-        pgettext_lazy('Address field', 'city'),
-        max_length=256)
+        pgettext_lazy('Address field', 'city'), max_length=256)
+    street_address = models.CharField(
+        pgettext_lazy('Address field', 'street address'), max_length=256)
     postal_code = models.CharField(
-        pgettext_lazy('Address field', 'postal code'),
-        max_length=20)
+        pgettext_lazy('Address field', 'postal code'), max_length=20)
     country = models.CharField(
         pgettext_lazy('Address field', 'country'),
         choices=COUNTRY_CHOICES, max_length=2)
-    country_area = models.CharField(
-        pgettext_lazy('Address field', 'country administrative area'),
-        max_length=128, blank=True)
     phone = models.CharField(
         pgettext_lazy('Address field', 'phone number'),
         max_length=30, blank=True)
@@ -114,17 +98,14 @@ class Address(models.Model):
     objects = AddressManager()
 
     def __str__(self):
-        return '%s %s' % (self.first_name, self.last_name)
+        return self.name
 
     def __repr__(self):
         return (
-            'Address(first_name=%r, last_name=%r, company_name=%r, '
-            'street_address_1=%r, street_address_2=%r, city=%r, '
-            'postal_code=%r, country=%r, country_area=%r, phone=%r)' % (
-                self.first_name, self.last_name, self.company_name,
-                self.street_address_1, self.street_address_2, self.city,
-                self.postal_code, self.country, self.country_area,
-                self.phone))
+            'Address(name=%r, street_address=%r, city=%r, postal_code=%r,'
+            ' country=%r, phone=%r)' % (
+                self.name, self.street_address, self.city, self.postal_code,
+                self.country, self.phone))
 
 
 class UserManager(BaseUserManager):
diff --git a/saleor/userprofile/views.py b/saleor/userprofile/views.py
--- a/saleor/userprofile/views.py
+++ b/saleor/userprofile/views.py
@@ -5,8 +5,9 @@
 from django.shortcuts import get_object_or_404, redirect
 from django.template.response import TemplateResponse
 from django.views.decorators.http import require_POST
+from django.utils.translation import ugettext as _
 
-from .models import AddressBook
+from .models import AddressBook, Address
 from .forms import AddressBookForm, AddressForm
 
 
@@ -14,14 +15,14 @@
 def details(request):
 
     ctx = {'address_book': request.user.address_book.all()}
-    return TemplateResponse(request, "userprofile/details.html", ctx)
+    return TemplateResponse(request, 'userprofile/details.html', ctx)
 
 
 @login_required
 def orders(request):
 
     ctx = {'orders': request.user.orders.prefetch_related('groups')}
-    return TemplateResponse(request, "userprofile/orders.html", ctx)
+    return TemplateResponse(request, 'userprofile/orders.html', ctx)
 
 
 def validate_address_and_render(request, address_form, address_book_form,
@@ -29,13 +30,13 @@ def validate_address_and_render(request, address_form, address_book_form,
     if address_form.is_valid() and address_book_form.is_valid():
         address = address_form.save()
         address_book_form.instance.address = address
-        address_book = address_book_form.save()
-        messages.success(request, success_message % address_book)
-        return HttpResponseRedirect(reverse("profile:details"))
+        address_book_form.save()
+        messages.success(request, success_message)
+        return HttpResponseRedirect(reverse('profile:details'))
 
     return TemplateResponse(
         request,
-        "userprofile/address-edit.html",
+        'userprofile/address-edit.html',
         {'address_form': address_form, 'address_book_form': address_book_form})
 
 
@@ -45,18 +46,14 @@ def address_edit(request, slug, pk):
     address_book = get_object_or_404(AddressBook, pk=pk, user=request.user)
     address = address_book.address
 
-    if not address_book.get_slug() == slug and request.method == "GET":
+    if not address_book.get_slug() == slug and request.method == 'GET':
         return HttpResponseRedirect(address_book.get_absolute_url())
 
-    if request.POST:
-        address_form = AddressForm(request.POST, instance=address)
-        address_book_form = AddressBookForm(
-            request.POST, instance=address_book)
-    else:
-        address_form = AddressForm(instance=address)
-        address_book_form = AddressBookForm(instance=address_book)
+    address_form = AddressForm(request.POST or None, instance=address)
+    address_book_form = AddressBookForm(
+        request.POST or None, instance=address_book)
 
-    message = "Successfully updated address '%s'."
+    message = _('Address successfully updated.')
 
     return validate_address_and_render(
         request, address_form, address_book_form, success_message=message)
@@ -65,18 +62,23 @@ def address_edit(request, slug, pk):
 @login_required
 def address_create(request):
 
-    if request.POST:
-        address_form = AddressForm(request.POST)
-        address_book_form = AddressBookForm(
-            request.POST, instance=AddressBook(user=request.user))
-    else:
-        address_form = AddressForm()
-        address_book_form = AddressBookForm()
+    address_form = AddressForm(request.POST or None)
+    address_book_form = AddressBookForm(
+        request.POST or None, instance=AddressBook(user=request.user))
 
-    message = "Successfully created address '%s'."
+    message = _('Address successfully created.')
 
-    return validate_address_and_render(
+    is_first_address = not Address.objects.exists()
+    response = validate_address_and_render(
         request, address_form, address_book_form, success_message=message)
+    address_book = address_book_form.instance
+    if address_book.pk and is_first_address:
+        user = request.user
+        user.default_shipping_address = address_book
+        user.default_billing_address = address_book
+        user.save(update_fields=[
+            'default_shipping_address', 'default_billing_address'])
+    return response
 
 
 @login_required
@@ -87,13 +89,12 @@ def address_delete(request, slug, pk):
     if not address_book.get_slug() == slug:
         raise Http404
 
-    if request.POST:
+    if request.method == 'POST':
         address_book.address.delete()
-        message = "Successfully deleted address '%s'."
-        messages.success(request, message % address_book)
-        return HttpResponseRedirect(reverse("profile:details"))
+        messages.success(request, _('Address successfully deleted.'))
+        return HttpResponseRedirect(reverse('profile:details'))
 
-    return TemplateResponse(request, "userprofile/address-delete.html",
+    return TemplateResponse(request, 'userprofile/address-delete.html',
                             context={'object': address_book})
 
 
@@ -103,15 +104,14 @@ def address_make_default(request, pk, purpose):
     user = request.user
 
     address_book = get_object_or_404(AddressBook, pk=pk, user=user)
-
     if purpose == 'shipping':
         user.default_shipping_address = address_book
+        user.save(update_fields=['default_shipping_address'])
     elif purpose == 'billing':
         user.default_billing_address = address_book
+        user.save(update_fields=['default_billing_address'])
     else:
         raise AssertionError(
-            "``purpose`` should be ``billing`` or ``shipping``")
-
-    user.save()
+            '``purpose`` should be ``billing`` or ``shipping``')
 
     return redirect('profile:details')


Test patch:
diff --git a/saleor/checkout/tests.py b/saleor/checkout/tests.py
--- a/saleor/checkout/tests.py
+++ b/saleor/checkout/tests.py
@@ -7,16 +7,13 @@
 from ..userprofile.models import Address
 
 NEW_ADDRESS = {
-    'first_name': 'Test',
-    'last_name': 'Test',
-    'street_address_1': 'Test',
-    'street_address_2': 'Test',
+    'name': 'Test',
+    'street_address': 'Test',
     'city': 'Test',
     'phone': '12345678',
     'postal_code': '987654',
-    'country': 'PL',
-    'country_area': '',
-    'company_name': 'Test'}
+    'country': 'PL'
+}
 
 
 class TestBaseAddressStep(TestCase):
@@ -31,7 +28,7 @@ def test_new_method(self):
         request.POST = NEW_ADDRESS.copy()
         step = BaseAddressStep(request, {}, Address(**NEW_ADDRESS))
         self.assertTrue(step.forms_are_valid(), "Forms don't validate.")
-        self.assertEqual(step.address.first_name, 'Test')
+        self.assertEqual(step.address.name, 'Test')
 
 
 class TestBillingAddressStep(TestCase):
@@ -44,7 +41,7 @@ def test_address_save_without_address(self):
         step = BillingAddressStep(request, storage)
         self.assertEquals(step.process(), None)
         self.assertEqual(type(storage['address']), dict)
-        self.assertEqual(storage['address']['first_name'], 'Test')
+        self.assertEqual(storage['address']['name'], 'Test')
 
     def test_address_save_with_address_in_checkout(self):
         request = MagicMock()
@@ -94,9 +91,7 @@ def test_address_save_with_address_in_checkout(self, mock_save):
         request.user.is_authenticated.return_value = False
         request.session = {}
         request.POST = dict(NEW_ADDRESS, method='dummy_shipping')
-        original_billing_address = {'first_name': 'Change',
-                                    'last_name': 'Me',
-                                    'id': 10}
+        original_billing_address = {'name': 'Change Me', 'id': 10}
         group = MagicMock()
         group.address = None
         storage = {'address': original_billing_address}