Repository: saleor/saleor

Issue:
Users can create the same address multiple times
### Steps to reproduce the problem
1. Start checkout
1. Set a shipping address A
1. Set a billing address B
1. Place the order
1. Go to your account details
1. Edit the address B to have the same information than A
1. Checkout with A or B (or re-create it through a new address input?)
1. Place the order
1. The order should have failed because the same address exist two times: `get() returned more than one object`


### What I expected to happen
Have an error when updating the address, saying I already have that address.



Discussion:
Hey, is this project up for grabs, if so what part of the code the bug comes from? 
Hi! We need to make the address model fully unique and then add a check whenever an address is updated and double check the API and dashboard properly check for the uniqueness.

It's not a really good issue to pickup. But do feel free to create a pull request if you feel like it.

For now I don't have time to do it yet. On my fork I only added the unique constraint on addresses—https://github.com/NyanKiyoshi/saleor/commit/52ce5b1d691158a896ada5596066d7392d5da493.
I mean... I wouldn't say no to socks 😄 
Core team members will get them as well 😀

Resolving patch:
diff --git a/saleor/account/utils.py b/saleor/account/utils.py
--- a/saleor/account/utils.py
+++ b/saleor/account/utils.py
@@ -14,7 +14,11 @@
 
 def store_user_address(user, address, address_type):
     """Add address to user address book and set as default one."""
-    address, _ = user.addresses.get_or_create(**address.as_data())
+    address_data = address.as_data()
+
+    address = user.addresses.filter(**address_data).first()
+    if address is None:
+        address = user.addresses.create(**address_data)
 
     if address_type == AddressType.BILLING:
         if not user.default_billing_address:


Test patch:
diff --git a/tests/test_checkout.py b/tests/test_checkout.py
--- a/tests/test_checkout.py
+++ b/tests/test_checkout.py
@@ -10,8 +10,9 @@
 from prices import Money, TaxedMoney, TaxedMoneyRange
 
 from saleor.account import CustomerEvents
-from saleor.account.models import Address, CustomerEvent
-from saleor.checkout import views
+from saleor.account.models import Address, CustomerEvent, User
+from saleor.account.utils import store_user_address
+from saleor.checkout import AddressType, views
 from saleor.checkout.forms import CheckoutVoucherForm, CountryForm
 from saleor.checkout.utils import (
     add_variant_to_checkout,
@@ -1466,3 +1467,49 @@ def test_add_voucher_to_checkout_fail(
         add_voucher_to_checkout(checkout_with_item, voucher_with_high_min_spent_amount)
 
     assert checkout_with_item.voucher_code is None
+
+
+def test_store_user_address_uses_existing_one(address):
+    """Ensure storing an address that is already associated to the given user doesn't
+    create a new address, but uses the existing one instead.
+    """
+    user = User.objects.create_user("test@example.com", "password")
+    user.addresses.add(address)
+
+    expected_user_addresses_count = 1
+
+    store_user_address(user, address, AddressType.BILLING)
+
+    assert user.addresses.count() == expected_user_addresses_count
+    assert user.default_billing_address_id == address.pk
+
+
+def test_store_user_address_uses_existing_one_despite_duplicated(address):
+    """Ensure storing an address handles the possibility of an user
+    having the same address associated to them multiple time is handled properly.
+
+    It should use the first identical address associated to the user.
+    """
+    same_address = Address.objects.create(**address.as_data())
+    user = User.objects.create_user("test@example.com", "password")
+    user.addresses.set([address, same_address])
+
+    expected_user_addresses_count = 2
+
+    store_user_address(user, address, AddressType.BILLING)
+
+    assert user.addresses.count() == expected_user_addresses_count
+    assert user.default_billing_address_id == address.pk
+
+
+def test_store_user_address_create_new_address_if_not_associated(address):
+    """Ensure storing an address that is not associated to the given user
+    triggers the creation of a new address, but uses the existing one instead.
+    """
+    user = User.objects.create_user("test@example.com", "password")
+    expected_user_addresses_count = 1
+
+    store_user_address(user, address, AddressType.BILLING)
+
+    assert user.addresses.count() == expected_user_addresses_count
+    assert user.default_billing_address_id != address.pk