Skip to content

Commit

Permalink
Merge pull request #38 from Saku-SE/develop
Browse files Browse the repository at this point in the history
Develop: merge onto main for releasing subscription and wallet feature on server
  • Loading branch information
aroodgar committed Jun 2, 2023
2 parents 6f0b44a + c6ab903 commit 38a2d0a
Show file tree
Hide file tree
Showing 21 changed files with 444 additions and 5 deletions.
3 changes: 2 additions & 1 deletion saku/saku/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"comment",
"homepage",
"chat",
"support"
"support",
"subscription",
]

MIDDLEWARE = [
Expand Down
1 change: 1 addition & 0 deletions saku/saku/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
path("homepage/", include("homepage.urls")),
path("chat/", include("chat.urls")),
path("support/", include("support.urls")),
path("subscription/", include("subscription.urls")),
]

# url for user profile images:
Expand Down
Empty file added saku/subscription/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions saku/subscription/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from django.contrib import admin
from .models import Subscription

admin.site.register(Subscription)
6 changes: 6 additions & 0 deletions saku/subscription/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class SubscriptionConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'subscription'
24 changes: 24 additions & 0 deletions saku/subscription/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.0.5 on 2023-05-16 19:01

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Subscription',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=40, unique=True)),
('description', models.CharField(blank=True, max_length=200)),
('duration', models.IntegerField()),
('price', models.IntegerField()),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.0.5 on 2023-05-17 11:19

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('subscription', '0001_initial'),
]

operations = [
migrations.RenameField(
model_name='subscription',
old_name='duration',
new_name='usage_limit',
),
]
Empty file.
7 changes: 7 additions & 0 deletions saku/subscription/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.db import models

class Subscription(models.Model):
name = models.CharField(max_length=40, blank=False, null=False, unique=True)
description = models.CharField(max_length=200, blank=True)
usage_limit = models.IntegerField(null=False, blank=False)
price = models.IntegerField(null=False, blank=False)
8 changes: 8 additions & 0 deletions saku/subscription/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from rest_framework import serializers
from .models import Subscription

class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = "__all__"

89 changes: 89 additions & 0 deletions saku/subscription/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import json

from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework import status
from rest_framework.exceptions import ErrorDetail
from rest_framework.test import APIClient, APITestCase

from .models import Subscription
from user_profile.models import Profile


class SubscriptionTest(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
username="test_user", password="Ab654321", email="email@email.com"
)
self.user.is_active = True
self.user.save()
self.subscription1 = Subscription.objects.create(
name="name1", description="description1", usage_limit=10, price=15
)
self.subscription2 = Subscription.objects.create(
name="name2", description="description2", usage_limit=15, price=40
)
self.profile = Profile.objects.create(user=self.user, email=self.user.email, wallet=30)
self.client.force_authenticate(self.user)

def test_subscription_list_view(self):
url = reverse("subscription:subscription-list")

response = self.client.get(url, format="json")
self.assertEqual(status.HTTP_200_OK, response.status_code)
self.assertEqual(len(response.data), 2)

def test_purchase_subscription_success(self):
url = reverse("subscription:purchase-subscription")
data = {"id": self.subscription1.id}

response = self.client.post(url, data, format="json")
self.assertEqual(status.HTTP_200_OK, response.status_code)
self.assertEqual(response.data["data"]["type"], self.subscription1.name)

def test_purchase_subscription_invalid_purchase_already_active(self):
url = reverse("subscription:purchase-subscription")
data = {"id": self.subscription1.id}

response = self.client.post(url, data, format="json")
self.assertEqual(status.HTTP_200_OK, response.status_code)
self.assertEqual(response.data["data"]["type"], self.subscription1.name)

response = self.client.post(url, data, format="json")
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
self.assertEqual(response.data["message"], "Invalid purchase")

def test_purchase_subscription_insufficient_funds(self):
url = reverse("subscription:purchase-subscription")
data = {"id": self.subscription2.id}

response = self.client.post(url, data, format="json")
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
self.assertEqual(response.data["message"], "Insufficient funds")

def test_user_active_subscription_empty(self):
url = reverse("subscription:user-active-subscription")

response = self.client.get(url, format="json")
self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)

def test_user_active_subscription_not_empty(self):
url = reverse("subscription:purchase-subscription")
data = {"id": self.subscription1.id}

response = self.client.post(url, data, format="json")
self.assertEqual(status.HTTP_200_OK, response.status_code)
self.assertEqual(response.data["data"]["type"], self.subscription1.name)

url = reverse("subscription:user-active-subscription")

response = self.client.get(url, format="json")
self.assertEqual(status.HTTP_200_OK, response.status_code)
self.assertEqual(response.data["id"], self.subscription1.id)
self.assertEqual(response.data["name"], self.subscription1.name)
self.assertEqual(response.data["left_time_in_days"], 30)




9 changes: 9 additions & 0 deletions saku/subscription/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path
from .views import SubscriptionListView, PurchaseSubscriptionView, UserActiveSubscriptionInfoView

app_name = "subscription"
urlpatterns = [
path("list", SubscriptionListView.as_view(), name="subscription-list"),
path("purchase", PurchaseSubscriptionView.as_view(), name="purchase-subscription"),
path("active", UserActiveSubscriptionInfoView.as_view(), name="user-active-subscription")
]
62 changes: 62 additions & 0 deletions saku/subscription/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from rest_framework import generics, status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .models import Subscription
from user_profile.models import Profile
from .serializers import SubscriptionSerializer
from django.shortcuts import get_object_or_404
import datetime
from django.db import transaction

class SubscriptionListView(generics.ListAPIView):
permission_classes = (IsAuthenticated,)
queryset = Subscription.objects.all()
serializer_class = SubscriptionSerializer

class PurchaseSubscriptionView(generics.GenericAPIView):
permission_classes = (IsAuthenticated,)

def post(self, request, *args, **kwargs):
profile = Profile.objects.get(user=request.user)
if not profile.subscription is None:
return Response({
"message": "Invalid purchase",
"detail": "You have an already active purchase."
}, status=status.HTTP_400_BAD_REQUEST)
subscription = get_object_or_404(Subscription, id=request.data['id'])
if profile.wallet < subscription.price:
return Response({
"message": "Insufficient funds",
"detail": "You don't have enough credit in your wallet"
}, status=status.HTTP_400_BAD_REQUEST)

with transaction.atomic():
profile.wallet -= subscription.price
profile.subscription = subscription
profile.subscription_date = datetime.datetime.now()
profile.save()
return Response({
"status": "success",
"code": status.HTTP_200_OK,
"data": {
"type": subscription.name,
"usage_limit": subscription.usage_limit,
"left_time_in_days": 30,
}
}, status=status.HTTP_200_OK)

class UserActiveSubscriptionInfoView(generics.GenericAPIView):
permission_classes = (IsAuthenticated,)

def get(self, request):
profile = Profile.objects.get(user=request.user)
active_subscription = profile.subscription
if active_subscription is None:
return Response({}, status=status.HTTP_204_NO_CONTENT)
return Response({
"id": active_subscription.id,
"name": active_subscription.name,
"usage_limit": active_subscription.usage_limit,
"left_time_in_days": 30 - (datetime.datetime.now().date() - profile.subscription_date.date()).days
}, status=status.HTTP_200_OK)

18 changes: 18 additions & 0 deletions saku/user_profile/migrations/0008_profile_wallet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.0.5 on 2023-05-16 16:17

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('user_profile', '0007_followrelationship_and_more'),
]

operations = [
migrations.AddField(
model_name='profile',
name='wallet',
field=models.IntegerField(default=0),
),
]
20 changes: 20 additions & 0 deletions saku/user_profile/migrations/0009_profile_subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.0.5 on 2023-05-16 19:22

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('subscription', '0001_initial'),
('user_profile', '0008_profile_wallet'),
]

operations = [
migrations.AddField(
model_name='profile',
name='subscription',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='subscription.subscription'),
),
]
18 changes: 18 additions & 0 deletions saku/user_profile/migrations/0010_profile_subscription_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.0.5 on 2023-05-17 11:41

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('user_profile', '0009_profile_subscription'),
]

operations = [
migrations.AddField(
model_name='profile',
name='subscription_date',
field=models.DateTimeField(blank=True, default=None, null=True),
),
]
5 changes: 5 additions & 0 deletions saku/user_profile/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.core.validators import (MaxValueValidator, MinValueValidator,
RegexValidator)
from django.db import models
from subscription.models import Subscription

phone_validator = RegexValidator(
regex=r"^09\d{9}$", message="Phone number is invalid (.eg '09123456789')"
Expand Down Expand Up @@ -41,6 +42,10 @@ class Profile(models.Model):
province = models.CharField(max_length=20, blank=True)
address = models.CharField(max_length=50, blank=True)
profile_image = models.ImageField(upload_to=photo_path, null=True, blank=True)
wallet = models.IntegerField(default=0, null=False, blank=False)
subscription = models.ForeignKey(Subscription, on_delete=models.SET_NULL, default=None, null=True)
subscription_date = models.DateTimeField(null=True, blank=True, default=None)


def __str__(self):
return self.name
Expand Down
28 changes: 27 additions & 1 deletion saku/user_profile/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rest_framework import serializers

from .models import Profile, FollowRelationship
import datetime


class ProfileSerializer(serializers.ModelSerializer):
Expand All @@ -15,7 +16,32 @@ def validate_email(self, email):
raise serializers.ValidationError(
"Another user exists with this email address."
)
return email
return email

class PersonalProfileSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
subscription = serializers.SerializerMethodField()
subscription_left_days = serializers.SerializerMethodField()

class Meta:
model = Profile
fields = "__all__"

def get_user(self, obj: Profile):
return {
"id": obj.user.id,
"username": obj.user.username,
}

def get_subscription(self, obj: Profile):
return {
"id": obj.subscription.id,
"name": obj.subscription.name,
"usage_limit": obj.subscription.usage_limit,
}

def get_subscription_left_days(self, obj: Profile):
return 30 - (datetime.datetime.now().date() - obj.subscription_date.date()).days


class GeneralProfileSerializer(serializers.ModelSerializer):
Expand Down
Loading

0 comments on commit 38a2d0a

Please sign in to comment.