Skip to content

Commit

Permalink
Merge pull request #97 from django-oscar/feature/user-addresses
Browse files Browse the repository at this point in the history
Now it's possible to manage your address book (user addresses)
  • Loading branch information
specialunderwear committed May 9, 2017
2 parents a3a3ffd + fcbaeea commit 4ac34e9
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/source/usage/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Checkout serializers

.. autoclass:: oscarapi.serializers.checkout.OrderSerializer
.. autoclass:: oscarapi.serializers.checkout.OrderLineSerializer
.. autoclass:: oscarapi.serializers.checkout.UserAddressSerializer

Login serializers
-----------------
Expand Down
29 changes: 29 additions & 0 deletions oscarapi/serializers/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
Country = get_model('address', 'Country')
Repository = get_class('shipping.repository', 'Repository')

UserAddress = get_model('address', 'UserAddress')


class PriceSerializer(serializers.Serializer):
currency = serializers.CharField(
Expand Down Expand Up @@ -316,3 +318,30 @@ def _shipping_method(self, request, basket,
return shipping_method

return default


class UserAddressSerializer(OscarModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='useraddress-detail')
country = serializers.HyperlinkedRelatedField(
view_name='country-detail', queryset=Country.objects)

def create(self, validated_data):
request = self.context['request']
validated_data['user'] = request.user
return super(UserAddressSerializer, self).create(validated_data)

def update(self, instance, validated_data):
# to be sure that we cannot change the owner of an address. If you
# want this, please override the serializer
request = self.context['request']
validated_data['user'] = request.user
return super(
UserAddressSerializer, self).update(instance, validated_data)

class Meta:
model = UserAddress
fields = overridable('OSCARAPI_USERADDRESS_FIELDS', (
'id', 'title', 'first_name', 'last_name', 'line1', 'line2',
'line3', 'line4', 'state', 'postcode', 'search_text',
'phone_number', 'notes', 'is_default_for_shipping',
'is_default_for_billing', 'country', 'url'))
186 changes: 186 additions & 0 deletions oscarapi/tests/testuseraddress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
from copy import deepcopy

from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse

from oscarapi.tests.utils import APITest

User = get_user_model()

ADDRESS = {
"title": "Mr",
"first_name": "Jos",
"last_name": "Henken",
"line1": "Roemerlaan 44",
"line2": "",
"line3": "",
"line4": "Kroekingen",
"state": "",
"postcode": "7777KK",
"phone_number": "+31 26 370 4887",
"notes": "Please deliver after 5pm",
"is_default_for_shipping": True,
"is_default_for_billing": False,
"country": "http://127.0.0.1:8000/api/countries/NL/",
}


class UserAddressTest(APITest):
"""
let's check the user addresses api
"""
fixtures = ['country']

def test_useraddress_anonymous(self):
url = reverse('useraddress-list')
self.response = self.client.get(url, content_type='application/json')

# not logged in, no glory
self.assertEqual(self.response.status_code, 403)

def test_useraddress_list_and_add(self):
"Regular users can list and add own addresses"
self.login('nobody', 'nobody')

url = reverse('useraddress-list')
self.response = self.client.get(url, content_type='application/json')

# no addresses yet
self.assertEqual(self.response.status_code, 200)
self.assertEqual(len(self.response.data), 0)

# let's create one
self.response = self.post(url, **ADDRESS)
self.assertEqual(self.response.status_code, 201)

# And now there should be one
self.response = self.client.get(url, content_type='application/json')
self.assertEqual(self.response.status_code, 200)
self.assertEqual(len(self.response.data), 1)

# after logout we don't see anything
self.client.logout()
self.response = self.client.get(url, content_type='application/json')
self.assertEqual(self.response.status_code, 403)

def test_useraddress_update_and_delete(self):
"Regular users can update and delete their own addresses"
self.login('nobody', 'nobody')

url = reverse('useraddress-list')
# let's create one
self.response = self.post(url, **ADDRESS)

# there is one, let's go to the detail view
self.response = self.client.get(url, content_type='application/json')
self.assertEqual(self.response.status_code, 200)
self.assertEqual(len(self.response.data), 1)

url = self.response.data[0]['url']
self.response = self.client.get(url, content_type='application/json')
self.assertEqual(self.response.status_code, 200)

# test some of the fields
self.assertEqual(self.response.data['last_name'], "Henken")
self.assertEqual(self.response.data['is_default_for_shipping'], True)
self.assertEqual(
self.response.data['notes'], "Please deliver after 5pm")

# now update the last name
updated_address = deepcopy(ADDRESS)
updated_address['last_name'] = 'Van Henken'
self.response = self.put(url, **updated_address)
self.assertEqual(self.response.status_code, 200)
self.assertEqual(self.response.data['last_name'], "Van Henken")

# refetching it should not make any difference
self.response = self.client.get(url, content_type='application/json')
self.assertEqual(self.response.data['last_name'], "Van Henken")

# let's delete it
self.response = self.client.delete(
url, content_type='application/json')

# it has been deleted, so a 204 should be the status code
self.assertEqual(self.response.status_code, 204)

# and it's gone
url = reverse('useraddress-list')
self.response = self.client.get(url, content_type='application/json')
self.assertEqual(self.response.status_code, 200)
self.assertEqual(len(self.response.data), 0)

def test_address_security(self):
"Make sure we can't access adresses of other users"
# first login as the first user
self.login('nobody', 'nobody')

nobodys_address = deepcopy(ADDRESS)
nobodys_address['last_name'] = 'Nobody'
url = reverse('useraddress-list')
self.response = self.post(url, **nobodys_address)

# save the address url
nobodys_address_url = self.response.data['url']

# and logout
self.client.logout()

# and now as the other one
self.login('somebody', 'somebody')
url = reverse('useraddress-list')
somebodys_address = deepcopy(ADDRESS)
somebodys_address['last_name'] = 'Somebody'
self.response = self.post(url, **somebodys_address)

# save the address url
somebodys_address_url = self.response.data['url']

# 'somebody' should not be able to access the address
# created by 'nobody'
self.response = self.client.get(
nobodys_address_url, content_type='application/json')
self.assertEqual(self.response.status_code, 404)

# and we can't delete it either
self.response = self.client.delete(
url, content_type='application/json')
self.assertEqual(self.response.status_code, 405)

# logout and login again with nobody
self.client.logout()
self.login('nobody', 'nobody')

# 'nobody' should not be able to access the address
# created by 'somebody'
self.response = self.client.get(
somebodys_address_url, content_type='application/json')
self.assertEqual(self.response.status_code, 404)

# and we can't update it either
somebodys_address['last_name'] = 'Hacked by nobody'
self.response = self.put(somebodys_address_url, **somebodys_address)
self.assertEqual(self.response.status_code, 404)

# now try to change the owner of my own address to 'somebody'
self.response = self.client.get(nobodys_address_url)
nobody_address = self.response.data
nobody_address['last_name'] = 'Hacked by somebody'
nobody_address['user'] = User.objects.get(username='somebody').id
self.response = self.put(nobodys_address_url, **nobody_address)

# oscarapi won't choke because it will just ignore the user param
self.assertEqual(self.response.status_code, 200)
self.response = self.client.get(nobodys_address_url)

# so in the end we only updated our own address
self.assertEqual(self.response.data['last_name'], 'Hacked by somebody')

# to be sure: somebody has only one address
self.client.logout()
self.login('somebody', 'somebody')

url = reverse('useraddress-list')
self.response = self.client.get(url, content_type='application/json')
self.assertEqual(self.response.status_code, 200)
self.assertEqual(len(self.response.data), 1)
4 changes: 3 additions & 1 deletion oscarapi/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
url(r'^countries/$', views.CountryList.as_view(), name='country-list'),
url(r'^countries/(?P<pk>[A-z]+)/$', views.CountryDetail.as_view(), name='country-detail'),
url(r'^partners/$', views.PartnerList.as_view(), name='partner-list'),
url(r'^partners/(?P<pk>[0-9]+)/$', views.PartnerDetail.as_view(), name='partner-detail')
url(r'^partners/(?P<pk>[0-9]+)/$', views.PartnerDetail.as_view(), name='partner-detail'),
url(r'^useraddresses/$', views.UserAddressList.as_view(), name='useraddress-list'),
url(r'^useraddresses/(?P<pk>[0-9]+)/$', views.UserAddressDetail.as_view(), name='useraddress-detail')
]

urlpatterns = format_suffix_patterns(urlpatterns)
Expand Down
25 changes: 23 additions & 2 deletions oscarapi/views/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
OrderSerializer,
CheckoutSerializer,
OrderLineSerializer,
OrderLineAttributeSerializer
OrderLineAttributeSerializer,
UserAddressSerializer,
)
from oscarapi.permissions import IsOwner
from oscarapi.views.utils import BasketPermissionMixin
Expand All @@ -15,11 +16,15 @@
OrderLine = get_model('order', 'Line')
OrderLineAttribute = get_model('order', 'LineAttribute')

UserAddress = get_model('address', 'UserAddress')

__all__ = (
'CheckoutView',
'OrderList', 'OrderDetail',
'OrderLineList', 'OrderLineDetail',
'OrderLineAttributeDetail'
'OrderLineAttributeDetail',
'UserAddressList',
'UserAddressDetail'
)


Expand Down Expand Up @@ -131,3 +136,19 @@ def post(self, request, format=None):
return response.Response(o_ser.data)

return response.Response(c_ser.errors, status.HTTP_406_NOT_ACCEPTABLE)


class UserAddressList(generics.ListCreateAPIView):
serializer_class = UserAddressSerializer
permission_classes = (IsOwner,)

def get_queryset(self):
return UserAddress.objects.filter(user=self.request.user)


class UserAddressDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = UserAddressSerializer
permission_classes = (IsOwner,)

def get_queryset(self):
return UserAddress.objects.filter(user=self.request.user)
1 change: 1 addition & 0 deletions oscarapi/views/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def PROTECTED_APIS(r, f):
('stockrecords', reverse('stockrecord-list', request=r, format=f)),
('users', reverse('user-list', request=r, format=f)),
('partners', reverse('partner-list', request=r, format=f)),
('useraddresses', reverse('useraddress-list', request=r, format=f)),
]


Expand Down

0 comments on commit 4ac34e9

Please sign in to comment.