├── .gitignore ├── Dockerfile ├── README.md ├── SplitwiseApp.postman_collection.json ├── docker-compose.yml ├── manage.py ├── requirements.txt ├── splitwise ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_rename_group_id_expense_group.py │ ├── 0003_alter_group_debts.py │ ├── 0004_auto_20211128_0641.py │ ├── 0005_auto_20211128_0655.py │ ├── 0006_alter_expense_expense_group.py │ ├── 0007_expense_name.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py └── splitwise_rest_api ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | .idea 4 | db.sqlite3 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM python:3.9.4-alpine 3 | 4 | # set work directory 5 | WORKDIR /usr/src/app 6 | 7 | # set environment variables 8 | ENV PYTHONDONTWRITEBYTECODE 1 9 | ENV PYTHONUNBUFFERED 1 10 | 11 | # install dependencies 12 | RUN pip install --upgrade pip 13 | COPY . . 14 | COPY ./requirements.txt . 15 | RUN pip install --no-cache -r requirements.txt 16 | # copy project 17 | COPY . . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Splitwise REST Interface - Python/Django 2 | 3 | REST based interface for Splitwise 4 | ___ 5 | **NOTE** 6 | 7 | names have been used as unique ids just for the sake of presentation, real world app should be based on UUID. 8 | 9 | --- 10 | 11 | ## Setup 12 | 13 | 1. Docker Based 14 | #### Prerequisites 15 | 1. Docker 16 | 2. run command 17 | 18 | ```shell 19 | docker-compose --build 20 | docker-compose up -d 21 | ``` 22 | This will expose 0.0.0.0:8000 port for API. 23 | 24 | 2. Local Setup 25 | ### Prerequisites 26 | 1. Python 3.9 27 | 2. Pip 28 | ```shell 29 | pip install -r requirements.txt 30 | ``` 31 | 32 | 3. Run the command 33 | ```python manage.py makemigrations && python manage.py migrate && python manage.py runserver 8000``` 34 | This will start the server at localhost:8000 35 | 36 | ### For Usage - Use postman collection provided alongside code 37 | Import the collection to postman and you will be able to see the endpoints and use them 38 | Also set an environment variable in postman 39 | 40 | Variable Name = url 41 | 42 | Variable Value = http://127.0.0.1:8000/api [local setup] or http://0.0.0.0:8000/api [for docker setup] 43 | 44 | ## The API Supports the below operations though rest endpoints: 45 | 1. Create User [/createUser] 46 | 2. Create Group [/createGroup] 47 | 3. Add member to group [/addUserToGroup] 48 | 4. Add personal expense between 2 existing users [/addExpense] 49 | 5. Add group expense [/addExpense] 50 | 6. Show Group Expenses [/groupDetails] 51 | 7. Show Group Members [/showGroupMembers] 52 | 8. Show the user details [/userDetails] 53 | 9. Record a personal payment [/recordPayment] 54 | 10. Record a group payment [/recordPayment] 55 | 11. Delete a user [/deleteUser] 56 | 12. Delete a group [/deleteGroup] 57 | 58 | ## Test Cases 59 | Test cases are available in ```splitwise/tests.py``` 60 | 61 | To run the test cases, run the command ```python manage.py test``` -------------------------------------------------------------------------------- /SplitwiseApp.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "a66159bd-bde3-4f2f-9c0a-6adcc5bd6c76", 4 | "name": "SplitwiseApp", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Create User", 10 | "request": { 11 | "method": "POST", 12 | "header": [], 13 | "body": { 14 | "mode": "raw", 15 | "raw": "{\n \"email\": \"abc2@gmail.com\",\n \"name\": \"ABC2\",\n \"password\": \"qwerty\"\n}", 16 | "options": { 17 | "raw": { 18 | "language": "json" 19 | } 20 | } 21 | }, 22 | "url": { 23 | "raw": "{{url}}/createUser", 24 | "host": [ 25 | "{{url}}" 26 | ], 27 | "path": [ 28 | "createUser" 29 | ] 30 | } 31 | }, 32 | "response": [] 33 | }, 34 | { 35 | "name": "Create Group", 36 | "request": { 37 | "method": "POST", 38 | "header": [], 39 | "body": { 40 | "mode": "raw", 41 | "raw": "{\n \"group_name\": \"test1\",\n \"members\": [\n \"abc@gmail.com\"\n ]\n}", 42 | "options": { 43 | "raw": { 44 | "language": "json" 45 | } 46 | } 47 | }, 48 | "url": { 49 | "raw": "{{url}}/createGroup", 50 | "host": [ 51 | "{{url}}" 52 | ], 53 | "path": [ 54 | "createGroup" 55 | ], 56 | "query": [ 57 | { 58 | "key": "group_name", 59 | "value": "test1", 60 | "disabled": true 61 | } 62 | ] 63 | } 64 | }, 65 | "response": [] 66 | }, 67 | { 68 | "name": "Add member to Group", 69 | "request": { 70 | "method": "POST", 71 | "header": [], 72 | "body": { 73 | "mode": "raw", 74 | "raw": "{\n \"group_name\":\"test1\",\n \"user_email\": \"abc2@gmail.com\"\n}", 75 | "options": { 76 | "raw": { 77 | "language": "json" 78 | } 79 | } 80 | }, 81 | "url": { 82 | "raw": "{{url}}/addUserToGroup", 83 | "host": [ 84 | "{{url}}" 85 | ], 86 | "path": [ 87 | "addUserToGroup" 88 | ] 89 | } 90 | }, 91 | "response": [] 92 | }, 93 | { 94 | "name": "Show Group Expenses", 95 | "request": { 96 | "method": "GET", 97 | "header": [], 98 | "url": { 99 | "raw": "{{url}}/groupDetails?name=test1", 100 | "host": [ 101 | "{{url}}" 102 | ], 103 | "path": [ 104 | "groupDetails" 105 | ], 106 | "query": [ 107 | { 108 | "key": "name", 109 | "value": "test1" 110 | } 111 | ] 112 | } 113 | }, 114 | "response": [] 115 | }, 116 | { 117 | "name": "View Group Members", 118 | "request": { 119 | "method": "GET", 120 | "header": [], 121 | "url": { 122 | "raw": "{{url}}/showGroupMembers?name=test1", 123 | "host": [ 124 | "{{url}}" 125 | ], 126 | "path": [ 127 | "showGroupMembers" 128 | ], 129 | "query": [ 130 | { 131 | "key": "name", 132 | "value": "test1" 133 | } 134 | ] 135 | } 136 | }, 137 | "response": [] 138 | }, 139 | { 140 | "name": "Get User Details", 141 | "request": { 142 | "method": "GET", 143 | "header": [], 144 | "url": { 145 | "raw": "{{url}}/userDetails?email=abc@gmail.com", 146 | "host": [ 147 | "{{url}}" 148 | ], 149 | "path": [ 150 | "userDetails" 151 | ], 152 | "query": [ 153 | { 154 | "key": "email", 155 | "value": "abc@gmail.com" 156 | } 157 | ] 158 | } 159 | }, 160 | "response": [] 161 | }, 162 | { 163 | "name": "Add Personal Expense", 164 | "request": { 165 | "method": "POST", 166 | "header": [], 167 | "body": { 168 | "mode": "raw", 169 | "raw": "{\n \"users\" : [\n \"abc@gmail.com\",\n \"abc2@gmail.com\"\n ],\n \"description\": \"Party expense2\",\n \"amount\": 500,\n \"paid_by\": \"abc@gmail.com\",\n \"name\": \"expense2\"\n}", 170 | "options": { 171 | "raw": { 172 | "language": "json" 173 | } 174 | } 175 | }, 176 | "url": { 177 | "raw": "{{url}}/addExpense", 178 | "host": [ 179 | "{{url}}" 180 | ], 181 | "path": [ 182 | "addExpense" 183 | ] 184 | } 185 | }, 186 | "response": [] 187 | }, 188 | { 189 | "name": "Add Group Expense", 190 | "request": { 191 | "method": "POST", 192 | "header": [], 193 | "body": { 194 | "mode": "raw", 195 | "raw": "{\n \"users\" : [\n \"abc@gmail.com\",\n \"abc2@gmail.com\"\n ],\n \"description\": \"Party expense123\",\n \"amount\": 1400,\n \"paid_by\": \"abc@gmail.com\",\n \"group_name\": \"test1\",\n \"name\":\"expense12345\"\n}", 196 | "options": { 197 | "raw": { 198 | "language": "json" 199 | } 200 | } 201 | }, 202 | "url": { 203 | "raw": "{{url}}/addExpense", 204 | "host": [ 205 | "{{url}}" 206 | ], 207 | "path": [ 208 | "addExpense" 209 | ] 210 | } 211 | }, 212 | "response": [] 213 | }, 214 | { 215 | "name": "Delete User", 216 | "request": { 217 | "method": "DELETE", 218 | "header": [], 219 | "body": { 220 | "mode": "raw", 221 | "raw": "", 222 | "options": { 223 | "raw": { 224 | "language": "json" 225 | } 226 | } 227 | }, 228 | "url": { 229 | "raw": "{{url}}/deleteUser?email=abc@gmail.com", 230 | "host": [ 231 | "{{url}}" 232 | ], 233 | "path": [ 234 | "deleteUser" 235 | ], 236 | "query": [ 237 | { 238 | "key": "email", 239 | "value": "abc@gmail.com" 240 | } 241 | ] 242 | } 243 | }, 244 | "response": [] 245 | }, 246 | { 247 | "name": "Delete Group", 248 | "request": { 249 | "method": "DELETE", 250 | "header": [], 251 | "body": { 252 | "mode": "raw", 253 | "raw": "", 254 | "options": { 255 | "raw": { 256 | "language": "json" 257 | } 258 | } 259 | }, 260 | "url": { 261 | "raw": "{{url}}/deleteGroup?name=test1", 262 | "host": [ 263 | "{{url}}" 264 | ], 265 | "path": [ 266 | "deleteGroup" 267 | ], 268 | "query": [ 269 | { 270 | "key": "name", 271 | "value": "test1" 272 | } 273 | ] 274 | } 275 | }, 276 | "response": [] 277 | }, 278 | { 279 | "name": "Record Personal Payment", 280 | "request": { 281 | "method": "POST", 282 | "header": [], 283 | "body": { 284 | "mode": "raw", 285 | "raw": "{\n \"from_user\": \"abc2@gmail.com\",\n \"to_user\": \"abc@gmail.com\",\n \"amount\": 1000\n}", 286 | "options": { 287 | "raw": { 288 | "language": "json" 289 | } 290 | } 291 | }, 292 | "url": { 293 | "raw": "{{url}}/recordPayment", 294 | "host": [ 295 | "{{url}}" 296 | ], 297 | "path": [ 298 | "recordPayment" 299 | ] 300 | } 301 | }, 302 | "response": [] 303 | }, 304 | { 305 | "name": "Record Expense Payment", 306 | "request": { 307 | "method": "POST", 308 | "header": [], 309 | "body": { 310 | "mode": "raw", 311 | "raw": "{\n \"from_user\": \"abc2@gmail.com\",\n \"to_user\": \"abc@gmail.com\",\n \"amount\": 700,\n \"group_name\": \"test1\",\n \"expense_name\":\"expense1234\"\n}", 312 | "options": { 313 | "raw": { 314 | "language": "json" 315 | } 316 | } 317 | }, 318 | "url": { 319 | "raw": "{{url}}/recordPayment", 320 | "host": [ 321 | "{{url}}" 322 | ], 323 | "path": [ 324 | "recordPayment" 325 | ] 326 | } 327 | }, 328 | "response": [] 329 | } 330 | ] 331 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | web: 5 | build: . 6 | command: sh -c "python manage.py makemigrations && python manage.py migrate && python manage.py runserver 0.0.0.0:8000" 7 | ports: 8 | - "8000:8000" -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'splitwise_rest_api.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.4.1 2 | Django==3.2.9 3 | djangorestframework==3.12.4 4 | python-dateutil==2.8.2 5 | pytz==2021.3 6 | six==1.16.0 7 | sqlparse==0.4.2 8 | -------------------------------------------------------------------------------- /splitwise/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tushverma/splitwise/db8ce8f8c5f543341c4d5e957d4b2674b86d7811/splitwise/__init__.py -------------------------------------------------------------------------------- /splitwise/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from splitwise import models 3 | 4 | # Register your models here. 5 | 6 | 7 | admin.site.register(models.UserProfile) 8 | admin.site.register(models.Debt) 9 | admin.site.register(models.Group) 10 | admin.site.register(models.Expense) 11 | -------------------------------------------------------------------------------- /splitwise/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SplitwiseConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'splitwise' 7 | -------------------------------------------------------------------------------- /splitwise/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-28 03:27 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import uuid 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Balance', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('amount', models.IntegerField()), 21 | ], 22 | ), 23 | migrations.CreateModel( 24 | name='Debt', 25 | fields=[ 26 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 27 | ('amount', models.IntegerField()), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name='UserProfile', 32 | fields=[ 33 | ('password', models.CharField(max_length=128, verbose_name='password')), 34 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 35 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 36 | ('email', models.EmailField(max_length=255, unique=True)), 37 | ('name', models.CharField(max_length=255)), 38 | ('is_active', models.BooleanField(default=True)), 39 | ('is_staff', models.BooleanField(default=False)), 40 | ], 41 | options={ 42 | 'abstract': False, 43 | }, 44 | ), 45 | migrations.CreateModel( 46 | name='Group', 47 | fields=[ 48 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 49 | ('group_name', models.CharField(max_length=255, unique=True)), 50 | ('debts', models.ManyToManyField(to='splitwise.Debt')), 51 | ('members', models.ManyToManyField(to='splitwise.UserProfile')), 52 | ], 53 | ), 54 | migrations.CreateModel( 55 | name='FriendGroup', 56 | fields=[ 57 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 58 | ('balances', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='splitwise.balance')), 59 | ], 60 | ), 61 | migrations.CreateModel( 62 | name='ExpenseUser', 63 | fields=[ 64 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 65 | ('paid_share', models.IntegerField(default=0)), 66 | ('owed_share', models.IntegerField(default=0)), 67 | ('net_balance', models.IntegerField(default=0)), 68 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='splitwise.userprofile')), 69 | ], 70 | ), 71 | migrations.AddField( 72 | model_name='debt', 73 | name='from_user', 74 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_user', to='splitwise.userprofile'), 75 | ), 76 | migrations.AddField( 77 | model_name='debt', 78 | name='to_user', 79 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_user', to='splitwise.userprofile'), 80 | ), 81 | migrations.CreateModel( 82 | name='Friend', 83 | fields=[ 84 | ('userprofile_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='splitwise.userprofile')), 85 | ('balances', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='splitwise.balance')), 86 | ('groups', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='splitwise.group')), 87 | ], 88 | options={ 89 | 'abstract': False, 90 | }, 91 | bases=('splitwise.userprofile',), 92 | ), 93 | migrations.CreateModel( 94 | name='Expense', 95 | fields=[ 96 | ('description', models.CharField(max_length=255)), 97 | ('payment', models.BooleanField(default=False)), 98 | ('amount', models.IntegerField()), 99 | ('date', models.DateTimeField(auto_now_add=True)), 100 | ('transaction_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 101 | ('group_id', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='splitwise.group')), 102 | ('repayments', models.ManyToManyField(to='splitwise.Debt')), 103 | ('users', models.ManyToManyField(to='splitwise.ExpenseUser')), 104 | ('friendship_id', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='splitwise.friend')), 105 | ], 106 | ), 107 | ] 108 | -------------------------------------------------------------------------------- /splitwise/migrations/0002_rename_group_id_expense_group.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-28 03:47 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('splitwise', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='expense', 15 | old_name='group_id', 16 | new_name='group', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /splitwise/migrations/0003_alter_group_debts.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-28 04:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('splitwise', '0002_rename_group_id_expense_group'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='group', 15 | name='debts', 16 | field=models.ManyToManyField(null=True, to='splitwise.Debt'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /splitwise/migrations/0004_auto_20211128_0641.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-28 06:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('splitwise', '0003_alter_group_debts'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='friendgroup', 15 | name='balances', 16 | ), 17 | migrations.RemoveField( 18 | model_name='expense', 19 | name='friendship_id', 20 | ), 21 | migrations.DeleteModel( 22 | name='Friend', 23 | ), 24 | migrations.DeleteModel( 25 | name='FriendGroup', 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /splitwise/migrations/0005_auto_20211128_0655.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-28 06:55 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('splitwise', '0004_auto_20211128_0641'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='Balance', 15 | ), 16 | migrations.RenameField( 17 | model_name='expense', 18 | old_name='group', 19 | new_name='expense_group', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /splitwise/migrations/0006_alter_expense_expense_group.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-28 06:57 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('splitwise', '0005_auto_20211128_0655'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='expense', 16 | name='expense_group', 17 | field=models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='splitwise.group'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /splitwise/migrations/0007_expense_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-28 07:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('splitwise', '0006_alter_expense_expense_group'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='expense', 15 | name='name', 16 | field=models.CharField(default='1', max_length=255, unique=True), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /splitwise/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tushverma/splitwise/db8ce8f8c5f543341c4d5e957d4b2674b86d7811/splitwise/migrations/__init__.py -------------------------------------------------------------------------------- /splitwise/models.py: -------------------------------------------------------------------------------- 1 | import time 2 | import uuid 3 | 4 | from django.db import models 5 | from django.contrib.auth.models import AbstractBaseUser 6 | from django.contrib.auth.models import PermissionsMixin 7 | from django.contrib.auth.models import BaseUserManager 8 | from django.conf import settings 9 | 10 | 11 | class UserProfileManager(BaseUserManager): 12 | """ Manager for user profiles """ 13 | 14 | def create_user(self, email, name, password=None) -> "UserProfile": 15 | """Create a new user profile""" 16 | if not email: 17 | raise ValueError('Invalid Email') 18 | # normalize email, convert second half to lowercase 19 | email = self.normalize_email(email) 20 | user = self.model(email=email, name=name) 21 | user.set_password(password) 22 | user.save(using=self._db) 23 | return user 24 | 25 | 26 | class UserProfile(AbstractBaseUser): 27 | """ Database model for users in system """ 28 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 29 | email = models.EmailField(max_length=255, unique=True) 30 | name = models.CharField(max_length=255) 31 | is_active = models.BooleanField(default=True) 32 | is_staff = models.BooleanField(default=False) 33 | 34 | objects = UserProfileManager() 35 | 36 | USERNAME_FIELD = 'email' 37 | REQUIRED_FIELDS = ['name'] 38 | 39 | def get_full_name(self) -> str: 40 | """ 41 | Retrieve full name of user 42 | :return: str 43 | """ 44 | return self.name 45 | 46 | def get_short_name(self) -> str: 47 | """ 48 | Retrieve full name of user 49 | :return: str 50 | """ 51 | return self.name 52 | 53 | def __str__(self) -> str: 54 | """ 55 | Return String representation of User 56 | :return: str 57 | """ 58 | return f"Email: {self.email}, Name:{self.name}" 59 | 60 | 61 | class Debt(models.Model): 62 | from_user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='from_user') 63 | to_user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='to_user') 64 | amount = models.IntegerField() 65 | 66 | def __str__(self): 67 | return f'{self.to_user.name} owes {self.amount} to {self.from_user.name}' 68 | 69 | 70 | class Group(models.Model): 71 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 72 | group_name = models.CharField(max_length=255, unique=True) 73 | debts = models.ManyToManyField(Debt, null=True) 74 | members = models.ManyToManyField(UserProfile) 75 | 76 | 77 | class ExpenseUser(models.Model): 78 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 79 | user = models.ForeignKey(UserProfile, on_delete=models.CASCADE) 80 | paid_share = models.IntegerField(default=0) 81 | owed_share = models.IntegerField(default=0) 82 | net_balance = models.IntegerField(default=0) 83 | 84 | 85 | class Expense(models.Model): 86 | name = models.CharField(max_length=255, unique=True) 87 | expense_group = models.ForeignKey(Group, on_delete=models.DO_NOTHING, null=True, db_constraint=False) 88 | description = models.CharField(max_length=255) 89 | payment = models.BooleanField(default=False) 90 | amount = models.IntegerField() 91 | date = models.DateTimeField(auto_now_add=True) 92 | repayments = models.ManyToManyField(Debt) 93 | users = models.ManyToManyField(ExpenseUser) 94 | transaction_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 95 | -------------------------------------------------------------------------------- /splitwise/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from splitwise import models 3 | 4 | 5 | class UserProfileSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = models.UserProfile 8 | fields = ('id', 'email', 'name', 'password') 9 | extra_kwargs = {'password': {'write_only': True, 'style': {'input_type': 'password'}}} 10 | 11 | def create(self, validated_data): 12 | """Creates and returns a new user""" 13 | user = models.UserProfile.objects.create_user( 14 | email=validated_data['email'], 15 | name=validated_data['name'], 16 | password=validated_data['password'] 17 | ) 18 | return user 19 | 20 | def update(self, instance, validated_data): 21 | """Handle updating user account""" 22 | if 'password' in validated_data: 23 | password = validated_data.pop('password') 24 | instance.set_password(password) 25 | return super().update(instance, validated_data) 26 | 27 | 28 | class DebtSerializer(serializers.ModelSerializer): 29 | class Meta: 30 | model = models.Debt 31 | fields = ('id', 'from_user', 'to_user', 'amount') 32 | 33 | 34 | class GroupSerializer(serializers.ModelSerializer): 35 | class Meta: 36 | model = models.Group 37 | fields = ('id', 'group_name', 'debts', 'members') 38 | 39 | 40 | class ExpenseUserSerializer(serializers.ModelSerializer): 41 | class Meta: 42 | model = models.ExpenseUser 43 | fields = ('id', 'paid_share', 'owed_share', 'net_balance') 44 | 45 | 46 | class ExpenseSerializer(serializers.ModelSerializer): 47 | class Meta: 48 | model = models.Expense 49 | fields = ('group_id', 'description', 'payment', 50 | 'date', 'friendship_id', 'repayments', 51 | 'user', 'transaction_id') 52 | -------------------------------------------------------------------------------- /splitwise/tests.py: -------------------------------------------------------------------------------- 1 | import json, uuid, datetime 2 | from django.test import TestCase 3 | from rest_framework.test import APIRequestFactory, APIClient 4 | from splitwise import models, views 5 | 6 | 7 | class TestSplitwise(TestCase): 8 | def setUp(self): 9 | self.id = uuid.uuid4() 10 | self.client = APIClient() 11 | self.user1 = models.UserProfile.objects.create( 12 | name='ABC', 13 | email='abc@gmail.com', 14 | password='qwerty' 15 | ) 16 | self.user2 = models.UserProfile.objects.create( 17 | name='ABC2', 18 | email='abc2@gmail.com', 19 | password='qwerty' 20 | ) 21 | self.group = models.Group.objects.create( 22 | group_name='test123' 23 | ) 24 | self.user2.save() 25 | self.user1.save() 26 | self.group.save() 27 | 28 | def test_create_user(self): 29 | factory = APIRequestFactory() 30 | view = views.UserProfileApiView.as_view() 31 | # create 2 users 32 | request = factory.post('/createUser', 33 | json.dumps({ 34 | "email": "abc3@gmail.com", 35 | "name": "ABC3", 36 | "password": "qwerty" 37 | }), 38 | content_type='application/json') 39 | 40 | result = view(request) 41 | expected_response = { 42 | "message": "User ABC3 created successfully" 43 | } 44 | self.assertEqual(result.data, expected_response) 45 | 46 | def test_create_group(self): 47 | factory = APIRequestFactory() 48 | view = views.CreateGroupApiView.as_view() 49 | 50 | request = factory.post('/createGroup', 51 | json.dumps({ 52 | "group_name": "test2", 53 | "members": [ 54 | "abc@gmail.com" 55 | ] 56 | }), 57 | content_type='application/json') 58 | result = view(request) 59 | expected_response = { 60 | "message": "Group test2 Created successfully" 61 | } 62 | self.assertEqual(result.data, expected_response) 63 | 64 | def test_add_member_to_group(self): 65 | factory = APIRequestFactory() 66 | view = views.AddUserToGroupApiView.as_view() 67 | 68 | request = factory.post('/addUserToGroup', 69 | json.dumps({ 70 | "group_name": "test123", 71 | "user_email": "abc2@gmail.com" 72 | }), 73 | content_type='application/json') 74 | result = view(request) 75 | expected_response = { 76 | "message": "User abc2@gmail.com successfully added to group test123" 77 | } 78 | self.assertEqual(result.data, expected_response) 79 | 80 | def test_add_expense(self): 81 | factory = APIRequestFactory() 82 | view = views.CreateExpenseApiView.as_view() 83 | 84 | request = factory.post('/addExpense', 85 | json.dumps({ 86 | "users": [ 87 | "abc@gmail.com", 88 | "abc2@gmail.com" 89 | ], 90 | "description": "Party expense123", 91 | "amount": 1400, 92 | "paid_by": "abc@gmail.com", 93 | "group_name": "test123", 94 | "name": "expense12345" 95 | }), 96 | content_type='application/json') 97 | 98 | result = view(request) 99 | expected_response = { 100 | "message": "Expense Created successfully" 101 | } 102 | self.assertEqual(result.data, expected_response) 103 | -------------------------------------------------------------------------------- /splitwise/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework.routers import DefaultRouter 3 | from splitwise import views 4 | 5 | urlpatterns = [ 6 | path('createGroup', views.CreateGroupApiView.as_view()), 7 | path('createExpense', views.CreateExpenseApiView.as_view()), 8 | path('createUser', views.UserProfileApiView.as_view()), 9 | path('addUserToGroup', views.AddUserToGroupApiView.as_view()), 10 | path('showGroupMembers', views.ShowGroupMembersApiView.as_view()), 11 | path('userDetails', views.ShowUserDetailsApiView.as_view()), 12 | path('addExpense', views.CreateExpenseApiView.as_view()), 13 | path('groupDetails', views.ShowGroupDetailsApiView.as_view()), 14 | path('deleteUser', views.DeleteUserApiView.as_view()), 15 | path('deleteGroup', views.DeleteGroupApiView.as_view()), 16 | path('recordPayment', views.RecordPaymentApiView.as_view()), 17 | ] 18 | -------------------------------------------------------------------------------- /splitwise/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.views import APIView 2 | from rest_framework.response import Response 3 | from rest_framework import status 4 | from splitwise import serializers, models 5 | 6 | 7 | class UserProfileApiView(APIView): 8 | """Test API View""" 9 | serializer_class = serializers.UserProfileSerializer 10 | 11 | def post(self, request) -> Response: 12 | """Create a hello message with our name""" 13 | serializer = self.serializer_class(data=request.data) 14 | if serializer.is_valid(): 15 | serializer.save() 16 | name = serializer.validated_data.get('name') 17 | return Response({'message': f'User {name} created successfully'}) 18 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 19 | 20 | 21 | class CreateGroupApiView(APIView): 22 | """Group Creation View""" 23 | serializer_class = serializers.GroupSerializer 24 | 25 | def post(self, request) -> Response: 26 | """ Create a hello message with our name """ 27 | 28 | all_users = [] 29 | for user_email in request.data.get('members', []): 30 | all_users.append(models.UserProfile.objects.get(email=user_email).id) 31 | request.data['members'] = all_users 32 | serializer = self.serializer_class(data=request.data) 33 | if serializer.is_valid(): 34 | serializer.save() 35 | return Response({'message': f'Group {serializer.data.get("group_name")} Created successfully'}) 36 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 37 | 38 | 39 | class AddUserToGroupApiView(APIView): 40 | """Add member to existing group Creation View""" 41 | 42 | def post(self, request) -> Response: 43 | """ Create a hello message with our name """ 44 | group_name = request.data.get('group_name') 45 | user_email = request.data.get('user_email') 46 | user = models.UserProfile.objects.get(email=user_email) 47 | group = models.Group.objects.get(group_name=group_name) 48 | if user not in group.members.all(): 49 | group.members.add(user.id) 50 | return Response({'message': f'User {user_email} successfully added to group {group.group_name}'}) 51 | return Response({'message': 'User already exists in the group'}, status=status.HTTP_400_BAD_REQUEST) 52 | 53 | 54 | class ShowGroupMembersApiView(APIView): 55 | """Show group members""" 56 | 57 | def get(self, request) -> Response: 58 | """ Create a hello message with our name """ 59 | group_name = request.GET['name'] 60 | try: 61 | group = models.Group.objects.get(group_name=group_name) 62 | # all_members = [x.name for x in group.members] 63 | all_members = [str(x) for x in group.members.all()] 64 | return Response({'message': f'{all_members}'}) 65 | except models.Group.DoesNotExist: 66 | return Response( 67 | {'message': 'Group Does not exist !' 68 | }, 69 | status=status.HTTP_404_NOT_FOUND 70 | ) 71 | 72 | 73 | class ShowUserDetailsApiView(APIView): 74 | """Show user details""" 75 | 76 | def get(self, request) -> Response: 77 | user_email = request.GET['email'] 78 | try: 79 | user = models.UserProfile.objects.get(email=user_email) 80 | f_debts = models.Debt.objects.filter(from_user=user) 81 | t_debts = models.Debt.objects.filter(to_user=user) 82 | debt_data = dict() 83 | debit = 0 84 | credit = 0 85 | for i in f_debts: 86 | debt_data[i.to_user.name] = debt_data.get(i.to_user.name, 0) - i.amount 87 | debit -= i.amount 88 | for i in t_debts: 89 | debt_data[i.from_user.name] = debt_data.get(i.from_user.name, 0) + i.amount 90 | credit += i.amount 91 | return Response( 92 | {'message': 93 | { 94 | 'user': f'{user}', 95 | 'debit': debit, 96 | 'credit': credit, 97 | 'data': [f'User {user.name} ows {debt_data[x]} to user {x}' if debt_data[ 98 | x] > 0 else f'User {user.name} owes {-1 * debt_data[x]} to {x}' 99 | for x in debt_data if 100 | x != user.name and debt_data[x] != 0], 101 | } 102 | } 103 | ) 104 | except models.UserProfile.DoesNotExist: 105 | return Response( 106 | {'message': 'User Does not exist !' 107 | }, 108 | status=status.HTTP_404_NOT_FOUND 109 | ) 110 | 111 | 112 | class CreateExpenseApiView(APIView): 113 | """Group Creation View""" 114 | serializer_class = serializers.ExpenseSerializer 115 | 116 | def post(self, request) -> Response: 117 | description = request.data.get('description') 118 | all_users = request.data.get('users') 119 | all_users = models.UserProfile.objects.filter(email__in=all_users) 120 | paid_by = request.data.get('paid_by') 121 | paid_by_user = models.UserProfile.objects.filter(email=paid_by).first() 122 | amount = request.data.get('amount') 123 | group_name = request.data.get('group_name', None) 124 | expense_name = request.data.get('name') 125 | if models.Expense.objects.filter(name=expense_name).count() > 0: 126 | return Response({ 127 | "message": "Expense name should be unique" 128 | }, status=status.HTTP_400_BAD_REQUEST) 129 | group = None 130 | if group_name is not None: 131 | group = models.Group.objects.get(group_name=group_name) 132 | per_member_share = amount / len(all_users) 133 | expense_users = [] 134 | repayments = [] 135 | for user in all_users: 136 | if user != paid_by_user: 137 | debt = models.Debt.objects.create(**{"from_user": paid_by_user, 138 | "to_user": user, 139 | "amount": per_member_share}) 140 | repayments.append(debt) 141 | expense_user_dict = {"user": user, 142 | "paid_share": 0 if user != paid_by_user else per_member_share, 143 | "owed_share": per_member_share, 144 | "net_balance": -per_member_share if user != paid_by_user else amount - per_member_share 145 | } 146 | expense_user = models.ExpenseUser.objects.create(**expense_user_dict) 147 | expense_users.append(expense_user) 148 | # now create expense 149 | expense = { 150 | 'expense_group': group, 151 | 'description': description, 152 | 'amount': amount, 153 | 'name': expense_name 154 | } 155 | expense = models.Expense.objects.create(**expense) 156 | expense.repayments.set(repayments) 157 | expense.users.set(expense_users) 158 | expense.save() 159 | return Response({'message': 'Expense Created successfully'}) 160 | 161 | 162 | class ShowGroupDetailsApiView(APIView): 163 | def get(self, request) -> Response: 164 | group_name = request.GET['name'] 165 | try: 166 | group = models.Group.objects.get(group_name=group_name) 167 | expenses = models.Expense.objects.filter(expense_group=group, payment=False) 168 | data = list() 169 | for expense in expenses: 170 | exp = { 171 | "name": expense.name, 172 | "Description": expense.description, 173 | "repayments": [str(x) for x in expense.repayments.all() if 174 | x.from_user != x.to_user and x.amount != 0] 175 | } 176 | data.append(exp) 177 | return Response( 178 | {'message': data 179 | } 180 | ) 181 | except models.Group.DoesNotExist: 182 | return Response( 183 | {'message': 'Group Does not exist !' 184 | }, 185 | status=status.HTTP_404_NOT_FOUND 186 | ) 187 | 188 | 189 | class DeleteUserApiView(APIView): 190 | def delete(self, request) -> Response: 191 | user_email = request.GET['email'] 192 | try: 193 | user = models.UserProfile.objects.get(email=user_email) 194 | if user: 195 | user.delete() 196 | return Response( 197 | {'message': 'User deleted' 198 | } 199 | ) 200 | except models.UserProfile.DoesNotExist: 201 | return Response({ 202 | "message": "User does not exist" 203 | }) 204 | 205 | 206 | class DeleteGroupApiView(APIView): 207 | def delete(self, request) -> Response: 208 | group_name = request.GET['name'] 209 | try: 210 | group = models.Group.objects.get(group_name=group_name) 211 | if group: 212 | group.delete() 213 | return Response( 214 | { 215 | 'message': 'Group deleted' 216 | } 217 | ) 218 | except models.Group.DoesNotExist: 219 | return Response({ 220 | "message": "Group does not exist" 221 | }) 222 | 223 | 224 | class RecordPaymentApiView(APIView): 225 | def post(self, request) -> Response: 226 | from_user_email = request.data.get('from_user') 227 | to_user_email = request.data.get('to_user') 228 | amount = request.data.get('amount') 229 | group_name = request.data.get('group_name') 230 | expense_name = request.data.get('expense_name') 231 | from_user = models.UserProfile.objects.get(email=from_user_email) 232 | to_user = models.UserProfile.objects.get(email=to_user_email) 233 | try: 234 | if group_name is None: 235 | 236 | models.Debt.objects.create(**{ 237 | "from_user": from_user, 238 | "to_user": to_user, 239 | "amount": amount 240 | }) 241 | return Response({ 242 | "message": "Payment recorded successfully" 243 | }) 244 | else: 245 | expense = models.Expense.objects.get(name=expense_name) 246 | if expense.expense_group != models.Group.objects.get(group_name=group_name): 247 | return Response({ 248 | "message": "Expense not in group, please check !" 249 | }, status=status.HTTP_400_BAD_REQUEST) 250 | flag = False 251 | for i in expense.repayments.all(): 252 | if i.from_user == to_user and i.to_user == from_user: 253 | flag = True 254 | i.amount = i.amount - amount 255 | i.save() 256 | break 257 | if not flag: 258 | debt = models.Debt.objects.create(**{ 259 | "from_user": to_user, 260 | "to_user": from_user, 261 | "amount": amount 262 | }) 263 | expense.repayments.add(debt) 264 | expense.save() 265 | flag = False 266 | for i in expense.repayments.all(): 267 | if i.amount > 0: 268 | flag = True 269 | break 270 | if not flag: 271 | expense.payment = True 272 | expense.save() 273 | return Response({ 274 | "message": "Expense Payment recorded successfully" 275 | }) 276 | except models.UserProfile.DoesNotExist: 277 | return Response({ 278 | "message": "User does not exist" 279 | }) 280 | except models.Expense.DoesNotExist: 281 | return Response({ 282 | "message": "Expense does not exist" 283 | }) 284 | -------------------------------------------------------------------------------- /splitwise_rest_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tushverma/splitwise/db8ce8f8c5f543341c4d5e957d4b2674b86d7811/splitwise_rest_api/__init__.py -------------------------------------------------------------------------------- /splitwise_rest_api/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for splitwise_rest_api project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'splitwise_rest_api.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /splitwise_rest_api/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for splitwise_rest_api project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.9. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'django-insecure-=12$+kpa44yg8=i^otuajc*i*pq89ndv*d+&9r&7&lm&%&=q43' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = False 27 | 28 | ALLOWED_HOSTS = ['*'] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'splitwise.apps.SplitwiseConfig', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'splitwise_rest_api.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [BASE_DIR / 'templates'] 59 | , 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'splitwise_rest_api.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': BASE_DIR / 'db.sqlite3', 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | 124 | # Default primary key field type 125 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 126 | 127 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 128 | -------------------------------------------------------------------------------- /splitwise_rest_api/urls.py: -------------------------------------------------------------------------------- 1 | """splitwise_rest_api URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('api/', include('splitwise.urls')) 22 | ] 23 | -------------------------------------------------------------------------------- /splitwise_rest_api/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for splitwise_rest_api project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'splitwise_rest_api.settings') 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------