Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
env=dev
SECRET_KEY=

DATABASE_ENGINE=django.db.backends.postgresql_psycopg2
DATABASE_NAME=
DATABASE_USER=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
8 changes: 8 additions & 0 deletions app/.idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions app/.idea/app.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

299 changes: 299 additions & 0 deletions app/.idea/inspectionProfiles/Project_Default.xml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions app/.idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions app/.idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions app/.idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions app/.idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 39 additions & 7 deletions app/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,38 @@
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""

import os
import environ
from datetime import timedelta
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

env = environ.Env()
environ.Env.read_env(os.path.join('.env'))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-h-h69cr05lmc*w4vtkf+5qltg8#&#xf8fe(v9j9oxs-*-^#vjd'
# SECRET_KEY = 'django-insecure-h-h69cr05lmc*w4vtkf+5qltg8#&#xf8fe(v9j9oxs-*-^#vjd'
SECRET_KEY = env('SECRET_KEY', None)


# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["*"]

CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOWED_ORIGINS = [
"http://localhost:80",
"http://localhost:8000",
"http://localhost",
"http://127.0.0.1"
]

CSRF_TRUSTED_ORIGINS = CORS_ALLOWED_ORIGINS

# Application definition

Expand All @@ -41,6 +55,7 @@
]

MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
Expand Down Expand Up @@ -76,8 +91,12 @@

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': env('DATABASE_ENGINE', None),
'NAME': env('DATABASE_NAME', None),
'USER': env('DATABASE_USER', None),
'PASSWORD': env('DATABASE_PASSWORD', None),
'HOST': env('DATABASE_HOST', None),
'PORT': env('DATABASE_PORT', None),
}
}

Expand Down Expand Up @@ -117,10 +136,23 @@
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

AUTH_USER_MODEL = 'core.User'
AUTH_USER_MODEL = 'core.User'

SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=3),
'UPDATE_LAST_LOGIN': True,
'AUTH_HEADER_TYPES': ('Bearer', 'Token',),
}

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ('rest_framework_simplejwt.authentication.JWTAuthentication',),
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
}
7 changes: 6 additions & 1 deletion app/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include("core.urls")),
]

urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
21 changes: 18 additions & 3 deletions app/core/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
"""Manage admin page for main app."""
from django.contrib import admin
from core.models import User, ProductCategory, Product, Order, OrderDetail

# from django.contrib import admin

# Register your models here.
class ProductModelAdmin(admin.ModelAdmin):
list_display = ["name", "description", "created_on", "updated_on"]
list_filter = ["price", "created_on"]
search_fields = ["name"]


class OrderModelAdmin(admin.ModelAdmin):
list_display = ["user", "created_on", "updated_on"]


admin.site.register(User)
admin.site.register(ProductCategory)
admin.site.register(OrderDetail)
admin.site.register(Product, ProductModelAdmin)
admin.site.register(Order, OrderModelAdmin)

29 changes: 29 additions & 0 deletions app/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from rest_framework.exceptions import APIException


class InvalidRequestException(APIException):
status_code = 400
default_detail = 'Invalid request'
default_code = 'invalid_request'


def raise_serializer_error_msg(errors: {}):
data = dict()
msg = ""
for err_key, err_val in errors.items():
if type(err_val) is list:
err_msg = ', '.join(err_val)
msg = f'Error occurred on \'{err_key.replace("_", " ")}\' field: {err_msg}'
else:
for err_val_key, err_val_val in err_val.items():
err_msg = ', '.join(err_val_val)
msg = f'Error occurred on \'{err_val_key.replace("_", " ")}\' field: {err_msg}'
data["detail"] = msg
raise InvalidRequestException(data)


def create_error_message(key, values):
data = dict()
data[key] = str(values).split('|')
raise InvalidRequestException({'detail': values})

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.6 on 2024-07-17 22:47

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


class Migration(migrations.Migration):

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

operations = [
migrations.CreateModel(
name='ProductCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('created_on', models.DateTimeField(auto_now_add=True)),
],
options={
'verbose_name_plural': 'Product Categories',
},
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('description', models.CharField(blank=True, max_length=200, null=True)),
('price', models.DecimalField(decimal_places=2, max_digits=20)),
('created_on', models.DateTimeField(auto_now_add=True)),
('updated_on', models.DateTimeField(auto_now=True)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.productcategory')),
],
),
migrations.CreateModel(
name='OrderDetail',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField(default=1)),
('total_price', models.DecimalField(decimal_places=2, max_digits=20)),
('created_on', models.DateTimeField(auto_now_add=True)),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')),
],
),
migrations.CreateModel(
name='Order',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(auto_now_add=True)),
('updated_on', models.DateTimeField(auto_now=True)),
('order_detail', models.ManyToManyField(to='core.orderdetail')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
54 changes: 53 additions & 1 deletion app/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, \
PermissionsMixin
PermissionsMixin


# Create your models here.


Expand Down Expand Up @@ -40,3 +42,53 @@ class User(AbstractBaseUser, PermissionsMixin):
objects = UserManager()

USERNAME_FIELD = 'email'


class ProductCategory(models.Model):
name = models.CharField(max_length=50)
created_on = models.DateTimeField(auto_now_add=True)

def __str__(self):
return str(self.name)

class Meta:
verbose_name_plural = "Product Categories"


class Product(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=200, blank=True, null=True)
price = models.DecimalField(decimal_places=2, max_digits=20)
category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)

def __str__(self):
return str(self.name)


class OrderDetail(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
total_price = models.DecimalField(decimal_places=2, max_digits=20)
created_on = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f"{self.product.name}: Quantity - {self.quantity}"


"""
I added the above model to cater for each product with different quantity on the Order table.
Customer may want to order product A - 1 quantity, and product B - 3 quantities
"""


# Create an Order model with fields like user (ForeignKey), product (ManyToManyField), quantity, and date.
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
order_detail = models.ManyToManyField(OrderDetail)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)

def __str__(self):
return f"{self.user.email}: OrderID - {self.id}"
7 changes: 7 additions & 0 deletions app/core/paginations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from rest_framework import pagination


class CustomPagination(pagination.PageNumberPagination):
page_size = 30
max_page_size = 100

Loading