Skip to content

Commit

Permalink
Ability to edit user profiles (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
ElinSwedin authored and kjagiello committed Mar 13, 2017
1 parent ad176f4 commit e094b4a
Show file tree
Hide file tree
Showing 16 changed files with 248 additions and 30 deletions.
3 changes: 2 additions & 1 deletion src/foobar/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class CardInline(admin.TabularInline):

@admin.register(models.Account)
class AccountAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'user', 'balance')
list_display = ('id', 'name', 'user', 'balance', 'email')
readonly_fields = ('id', 'wallet_link', 'date_created', 'date_modified')
inlines = (CardInline, PurchaseInline,)
search_fields = ('name',)
Expand All @@ -77,6 +77,7 @@ class AccountAdmin(admin.ModelAdmin):
'id',
'user',
'name',
'email'
)
}),
('Additional information', {
Expand Down
13 changes: 12 additions & 1 deletion src/foobar/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,23 @@ def get_card(card_id):
return None


def get_account(card_id):
def get_account(account_id):
try:
return Account.objects.get(id=account_id)
except Account.DoesNotExist:
return None


def get_account_by_card(card_id):
card_obj = get_card(card_id)
if card_obj is not None:
return card_obj.account


def update_account(account_id, **kwargs):
Account.objects.filter(id=account_id).update(**kwargs)


@transaction.atomic
def purchase(account_id, products):
"""
Expand Down
11 changes: 9 additions & 2 deletions src/foobar/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@


class CorrectionForm(forms.Form):
balance = MoneyField(label='Balance', min_value=0)
comment = forms.CharField(label='Comment', max_length=128, required=False)
balance = MoneyField(label=_('Balance'), min_value=0)
comment = forms.CharField(label=_('Comment'),
max_length=128,
required=False)


class DepositForm(forms.Form):
Expand All @@ -23,3 +25,8 @@ def clean_deposit_or_withdrawal(self):
if data.amount < 0 and -data > balance:
raise forms.ValidationError(_('Not enough funds'))
return data


class EditProfileForm(forms.Form):
name = forms.CharField(label=_("Account Name"), max_length=128)
email = forms.EmailField(label=_("E-mail"))
20 changes: 20 additions & 0 deletions src/foobar/migrations/0020_auto_20170302_1359.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-02 13:59
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('foobar', '0019_auto_20170221_1547'),
]

operations = [
migrations.AlterField(
model_name='account',
name='email',
field=models.EmailField(blank=True, max_length=254, null=True),
),
]
6 changes: 5 additions & 1 deletion src/foobar/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
class Account(UUIDModel, TimeStampedModel):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(null=True, blank=True, max_length=128)
email = models.CharField(null=True, blank=True, max_length=128)
email = models.EmailField(null=True, blank=True)

@property
def is_complete(self):
return bool(self.email)

def __str__(self):
return str(self.id)
Expand Down
7 changes: 7 additions & 0 deletions src/foobar/rest/serializers/account.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
from rest_framework import serializers
from foobar.wallet import api as wallet_api
from ..fields import MoneyField
from django.core import signing


class AccountSerializer(serializers.Serializer):
def get_balance(self, instance):
_, balance = wallet_api.get_balance(instance.id)
return MoneyField().to_representation(balance)

def get_token(self, instance):
token = signing.dumps({'id': str(instance.id)})
return token

id = serializers.UUIDField(read_only=True)
user_id = serializers.UUIDField(read_only=True, source='user.id')
name = serializers.CharField(read_only=True)
balance = serializers.SerializerMethodField()
token = serializers.SerializerMethodField()
is_complete = serializers.BooleanField(read_only=True)


class AccountQuerySerializer(serializers.Serializer):
Expand Down
3 changes: 2 additions & 1 deletion src/foobar/rest/views/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from rest_framework.response import Response
from authtoken.permissions import HasTokenScope


import foobar.api

from ..serializers.account import AccountQuerySerializer, AccountSerializer
Expand All @@ -13,7 +14,7 @@ class AccountAPI(viewsets.ViewSet):
def retrieve(self, request, pk):
serializer = AccountQuerySerializer(data={'card_id': pk})
serializer.is_valid(raise_exception=True)
account_obj = foobar.api.get_account(card_id=pk)
account_obj = foobar.api.get_account_by_card(card_id=pk)
if account_obj is None:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = AccountSerializer(account_obj)
Expand Down
44 changes: 44 additions & 0 deletions src/foobar/static/css/profile.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
body{
margin: 0;
font-size: 26px;
}
.info {
background: #dfd;
padding: 10px;
color: #333;
text-align: center;
}
#header{
background: #a70d0d;
font-size: 350%;
padding: 2%;
color: white;
text-align: center;
margin: auto;
border-bottom: 5px solid #d34a4a;
}
.account_form{
font-size: 180%;
width: 70%;
margin: auto;
padding-top: 5%
}
.account_form >*{
width: 100%;
height: 40px;
margin: 10px;
outline-color: #a70d0d;
}
input{
font-size: 26px;
}
#submit_changes{
background: #a70d0d;
color: white;
border: none;
padding: 5px;
height: 60px;
font-size: 100%;
-webkit-appearance: none;
border-radius: 0;
}
6 changes: 6 additions & 0 deletions src/foobar/static/css/scan_card.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@
margin-left: 0 !important;
margin-top: -4px !important;
}
.info{
background: #dfd url(../img/icon-yes.svg) 40px 12px no-repeat;
background-size: 16px auto;
text-align: center;

}
5 changes: 5 additions & 0 deletions src/foobar/templates/profile/bad_request.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% extends 'profile/base_profile.html' %}
{% load i18n %}
{% block content %}
<div class="account_form" style="text-align:center;">{% trans "Invalid QRcode" %} <br> {% trans "Login to get a new" %} </div>
{% endblock %}
17 changes: 17 additions & 0 deletions src/foobar/templates/profile/base_profile.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>FooBar</title>
<link rel="stylesheet" type="text/css" href="/static/css/profile.css"/>
</head>
<body>
{% load i18n %}
<div id="header" >
{% trans "Profile Details" %}
</div>
{% block content %}
{% endblock %}
</body>
</html>
17 changes: 17 additions & 0 deletions src/foobar/templates/profile/success.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% extends 'profile/base_profile.html' %}
{% load i18n %}
{% block content %}
<div style="width:100%; height:30px;">
{% if messages %}
{% for message in messages %}
<div class="info">{{ message }}</div>
{% endfor %}
{% endif %}
</div>
<form class="account_form" method="post" action="">
{% csrf_token %}
{{ form }}
<input type="submit" name="save_changes" value="{% trans 'Save' %} " id="submit_changes" value="{{ account_email }}">
</form>

{% endblock %}
27 changes: 25 additions & 2 deletions src/foobar/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .factories import AccountFactory, CardFactory
from moneyed import Money
from django.contrib.auth.models import User
import uuid


class FoobarAPITest(TestCase):
Expand All @@ -27,19 +28,41 @@ def test_get_card(self):
self.assertGreater(obj2.date_used, date_used)

def test_get_account(self):
# Assure None when missing account
id = uuid.uuid4()
obj1 = api.get_account(account_id=id)
self.assertIsNone(obj1)

# Create an account
account_obj = AccountFactory.create()
obj2 = api.get_account(account_id=account_obj.id)

self.assertIsNotNone(obj2)

def test_get_account_by_card(self):
# Retrieve an non-existent account
obj1 = api.get_account(card_id=1337)
obj1 = api.get_account_by_card(card_id=1337)
self.assertIsNone(obj1)

# Create an account
CardFactory.create(number=1337)
obj2 = api.get_account(card_id=1337)
obj2 = api.get_account_by_card(card_id=1337)

self.assertIsNotNone(obj2)

account_objs = models.Account.objects.filter(id=obj2.id)
self.assertEqual(account_objs.count(), 1)

def test_update_account(self):
account_obj = AccountFactory.create()
api.update_account(account_id=account_obj.id,
name='1337',
email='[email protected]')
account = api.get_account(account_id=account_obj.id)
# Test that correct fields are updated
self.assertEqual('1337', account.name)
self.assertEqual('[email protected]', account.email)

def test_purchase(self):
account_obj = AccountFactory.create()
wallet_obj = WalletFactory.create(owner_id=account_obj.id)
Expand Down
64 changes: 46 additions & 18 deletions src/foobar/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from wallet.tests.factories import WalletFactory, WalletTrxFactory
from wallet.enums import TrxType
from . import factories
from django.core import signing


class FoobarViewTest(TestCase):
Expand All @@ -23,7 +24,7 @@ def setUp(self):
password=self.TESTUSER_PASS
)

@mock.patch('foobar.api.get_account')
@mock.patch('foobar.api.get_account_by_card')
def test_account_for_card(self, mock_get_account):
url = reverse('account_for_card', kwargs={'card_id': 1337})
mock_get_account.return_value = None
Expand Down Expand Up @@ -53,38 +54,65 @@ def test_wallet_management(self, mock_deposit_withdrawal, mock_correction):
)
url = reverse('wallet_management',
kwargs={'obj_id': wallet_obj.owner_id})
cl = self.client
# Test that deposit or withdrawal
# is not called if balance will get negative
response = cl.post(url,
{'deposit_or_withdrawal_1': ['SEK'],
'save_deposit': ['Submit'],
'comment': ['test'],
'deposit_or_withdrawal_0': ['-3000']})
response = self.client.post(url,
{'deposit_or_withdrawal_1': ['SEK'],
'save_deposit': ['Submit'],
'comment': ['test'],
'deposit_or_withdrawal_0': ['-3000']})
mock_deposit_withdrawal.assert_not_called()
# Test that page can be found
response = cl.get(url)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# Test that correction form post is correct and
# calls function with correct params
response = cl.post(url,
{'save_correction': ['Submit'],
'balance_1': ['SEK'],
'comment': ['test'],
'balance_0': ['1000']})
self.client.post(url,
{'save_correction': ['Submit'],
'balance_1': ['SEK'],
'comment': ['test'],
'balance_0': ['1000']})
mock_correction.assert_called_with(Money(1000, 'SEK'),
wallet_obj.owner_id,
self.user,
'test')
# Test that deposit or withdrawal form post is correct and
# calls fucnction with correct params
response = cl.post(url,
{'deposit_or_withdrawal_1': ['SEK'],
'save_deposit': ['Submit'],
'comment': ['test'],
'deposit_or_withdrawal_0': ['100']})
self.client.post(url,
{'deposit_or_withdrawal_1': ['SEK'],
'save_deposit': ['Submit'],
'comment': ['test'],
'deposit_or_withdrawal_0': ['100']})
mock_deposit_withdrawal.assert_called_with(
Money(100, 'SEK'),
wallet_obj.owner_id,
self.user,
'test')

@mock.patch('foobar.api.update_account')
def test_edit_profile(self, mock_update_account):
account_obj = factories.AccountFactory.create()
token = signing.dumps({'id': str(account_obj.id)})
url = reverse('edit_profile', kwargs={'token': token})
bad_token = reverse('edit_profile', kwargs={'token': 'bad'})
response1 = self.client.get(url)
response2 = self.client.get(bad_token)

# Assert that page can be found
self.assertEqual(response1.status_code, 200)
self.assertEqual(response2.status_code, 200)

# Assure update_account not called when url with bad token send POST
self.client.post(bad_token, {'name': 'foo',
'email': '[email protected]',
'save_changes': ['Submit']})
mock_update_account.assert_not_called()

# Assure set_balance is called when token is valid
token_data = signing.loads(token, max_age=1800)
self.client.post(url, {'name': 'foo',
'email': '[email protected]',
'save_changes': ['Submit']})
mock_update_account.assert_called_with(token_data.get('id'),
name='foo',
email='[email protected]')
3 changes: 3 additions & 0 deletions src/foobar/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
url(r'^admin/foobar/account/card/(?P<card_id>\d+)',
foobar.views.account_for_card, name='account_for_card'),
url(r'^admin/', include(admin.site.urls)),
url(r'profile/(?P<token>.+)',
foobar.views.edit_profile,
name="edit_profile")
]

if settings.DEBUG:
Expand Down
Loading

0 comments on commit e094b4a

Please sign in to comment.