diff --git a/BudMan/__pycache__/settings.cpython-38.pyc b/BudMan/__pycache__/settings.cpython-38.pyc index 9b5363a..1b63595 100644 Binary files a/BudMan/__pycache__/settings.cpython-38.pyc and b/BudMan/__pycache__/settings.cpython-38.pyc differ diff --git a/BudMan/__pycache__/urls.cpython-38.pyc b/BudMan/__pycache__/urls.cpython-38.pyc index c16385a..3bf5e15 100644 Binary files a/BudMan/__pycache__/urls.cpython-38.pyc and b/BudMan/__pycache__/urls.cpython-38.pyc differ diff --git a/BudMan/settings.py b/BudMan/settings.py index 1bc1993..8740f34 100644 --- a/BudMan/settings.py +++ b/BudMan/settings.py @@ -1,15 +1,3 @@ -""" -Django settings for BudMan project. - -Generated by 'django-admin startproject' using Django 3.2.8. - -For more information on this file, see -https://docs.djangoproject.com/en/3.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.2/ref/settings/ -""" - from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -37,7 +25,8 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'app.apps.AppConfig', + 'api.apps.ApiConfig', + 'frontend.apps.FrontendConfig', ] MIDDLEWARE = [ diff --git a/BudMan/urls.py b/BudMan/urls.py index 44f0522..410de56 100644 --- a/BudMan/urls.py +++ b/BudMan/urls.py @@ -3,5 +3,6 @@ urlpatterns = [ path('admin/', admin.site.urls), - path('shop/', include('app.urls')), + path('api/', include('api.urls')), + path('', include('frontend.urls')), ] \ No newline at end of file diff --git a/app/__init__.py b/api/__init__.py similarity index 100% rename from app/__init__.py rename to api/__init__.py diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/app/apps.py b/api/apps.py similarity index 67% rename from app/apps.py rename to api/apps.py index ed327d2..66656fd 100644 --- a/app/apps.py +++ b/api/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class AppConfig(AppConfig): +class ApiConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'app' + name = 'api' diff --git a/app/migrations/__init__.py b/api/migrations/__init__.py similarity index 100% rename from app/migrations/__init__.py rename to api/migrations/__init__.py diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..137941f --- /dev/null +++ b/api/models.py @@ -0,0 +1 @@ +from django.db import models diff --git a/app/tests.py b/api/tests.py similarity index 100% rename from app/tests.py rename to api/tests.py diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..96afd02 --- /dev/null +++ b/api/urls.py @@ -0,0 +1,6 @@ +from django.urls import path + +from .views import * +urlpatterns = [ + # path('', home), +] \ No newline at end of file diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/__init__.cpython-38.pyc b/app/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index c7bc87f..0000000 Binary files a/app/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/app/__pycache__/admin.cpython-38.pyc b/app/__pycache__/admin.cpython-38.pyc deleted file mode 100644 index d9f2d7b..0000000 Binary files a/app/__pycache__/admin.cpython-38.pyc and /dev/null differ diff --git a/app/__pycache__/apps.cpython-38.pyc b/app/__pycache__/apps.cpython-38.pyc deleted file mode 100644 index 9398ad5..0000000 Binary files a/app/__pycache__/apps.cpython-38.pyc and /dev/null differ diff --git a/app/__pycache__/models.cpython-38.pyc b/app/__pycache__/models.cpython-38.pyc deleted file mode 100644 index 820a6b0..0000000 Binary files a/app/__pycache__/models.cpython-38.pyc and /dev/null differ diff --git a/app/__pycache__/urls.cpython-38.pyc b/app/__pycache__/urls.cpython-38.pyc deleted file mode 100644 index 52479a4..0000000 Binary files a/app/__pycache__/urls.cpython-38.pyc and /dev/null differ diff --git a/app/__pycache__/views.cpython-38.pyc b/app/__pycache__/views.cpython-38.pyc deleted file mode 100644 index 041111f..0000000 Binary files a/app/__pycache__/views.cpython-38.pyc and /dev/null differ diff --git a/app/admin.py b/app/admin.py deleted file mode 100644 index fc85b8e..0000000 --- a/app/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin -from app.models import Item, Purchase - -admin.site.register(Item) -admin.site.register(Purchase) \ No newline at end of file diff --git a/app/management/commands/populate_db.py b/app/management/commands/populate_db.py deleted file mode 100644 index 474d83e..0000000 --- a/app/management/commands/populate_db.py +++ /dev/null @@ -1,38 +0,0 @@ - -import random -from datetime import datetime, timedelta - -import pytz -from django.core.management.base import BaseCommand - -from app.models import Item, Purchase - - -class Command(BaseCommand): - help = 'Populates the database with random generated data.' - - def add_arguments(self, parser): - parser.add_argument('--amount', type=int, help='The number of purchases that should be created.') - - def handle(self, *args, **options): - names = ['James', 'John', 'Robert', 'Michael', 'William', 'David', 'Richard', 'Joseph', 'Thomas', 'Charles'] - surname = ['Smith', 'Jones', 'Taylor', 'Brown', 'Williams', 'Wilson', 'Johnson', 'Davies', 'Patel', 'Wright'] - items = [ - Item.objects.get_or_create(name='Socks', price=6.5), Item.objects.get_or_create(name='Pants', price=12), - Item.objects.get_or_create(name='T-Shirt', price=8), Item.objects.get_or_create(name='Boots', price=9), - Item.objects.get_or_create(name='Sweater', price=3), Item.objects.get_or_create(name='Underwear', price=9), - Item.objects.get_or_create(name='Leggings', price=7), Item.objects.get_or_create(name='Cap', price=5), - ] - amount = options['amount'] if options['amount'] else 2500 - for i in range(0, amount): - dt = pytz.utc.localize(datetime.now() - timedelta(days=random.randint(0, 1825))) - purchase = Purchase.objects.create( - customer_full_name=random.choice(names) + ' ' + random.choice(surname), - item=random.choice(items)[0], - payment_method=random.choice(Purchase.PAYMENT_METHODS)[0], - successful=True if random.randint(1, 2) == 1 else False, - ) - purchase.time = dt - purchase.save() - - self.stdout.write(self.style.SUCCESS('Successfully populated the database.')) \ No newline at end of file diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py deleted file mode 100644 index ca03c53..0000000 --- a/app/migrations/0001_initial.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.0 on 2021-12-17 14:11 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Item', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('description', models.TextField(null=True)), - ('price', models.FloatField(default=0)), - ], - ), - migrations.CreateModel( - name='Purchase', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('customer_full_name', models.CharField(max_length=64)), - ('payment_method', models.CharField(choices=[('CC', 'Credit card'), ('DC', 'Debit card'), ('ET', 'Ethereum'), ('BC', 'Bitcoin')], default='CC', max_length=2)), - ('time', models.DateTimeField(auto_now_add=True)), - ('successful', models.BooleanField(default=False)), - ('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.item')), - ], - options={ - 'ordering': ['-time'], - }, - ), - ] diff --git a/app/migrations/__pycache__/__init__.cpython-38.pyc b/app/migrations/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 4ec2e7b..0000000 Binary files a/app/migrations/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/app/models.py b/app/models.py deleted file mode 100644 index ab64283..0000000 --- a/app/models.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.db import models - -class Item(models.Model): - name = models.CharField(max_length=255) - description = models.TextField(null=True) - price = models.FloatField(default=0) - - def __str__(self): - return f'{self.name} (${self.price})' - - -class Purchase(models.Model): - customer_full_name = models.CharField(max_length=64) - item = models.ForeignKey(to=Item, on_delete=models.CASCADE) - PAYMENT_METHODS = [ - ('CC', 'Credit card'), - ('DC', 'Debit card'), - ('ET', 'Ethereum'), - ('BC', 'Bitcoin'), - ] - payment_method = models.CharField(max_length=2, default='CC', choices=PAYMENT_METHODS) - time = models.DateTimeField(auto_now_add=True) - successful = models.BooleanField(default=False) - - class Meta: - ordering = ['-time'] - - def __str__(self): - return f'{self.customer_full_name}, {self.payment_method} ({self.item.name})' \ No newline at end of file diff --git a/app/templates/app/statistics.html b/app/templates/app/statistics.html deleted file mode 100644 index d4a2861..0000000 --- a/app/templates/app/statistics.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - Statistics - - - - - -
-
- - - -
-
-
- -
-
- -
-
- -
-
- -
-
- -
- - - \ No newline at end of file diff --git a/app/urls.py b/app/urls.py deleted file mode 100644 index 8160b5c..0000000 --- a/app/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import path - -from . import views - -urlpatterns = [ - path('statistics/', views.statistics_view, name='shop-statistics'), - path('chart/filter-options/', views.get_filter_options, name='chart-filter-options'), - path('chart/sales//', views.get_sales_chart, name='chart-sales'), - path('chart/spend-per-customer//', views.spend_per_customer_chart, name='chart-spend-per-customer'), - path('chart/payment-success//', views.payment_success_chart, name='chart-payment-success'), - path('chart/payment-method//', views.payment_method_chart, name='chart-payment-method'), -] \ No newline at end of file diff --git a/app/views.py b/app/views.py deleted file mode 100644 index 7ad68bc..0000000 --- a/app/views.py +++ /dev/null @@ -1,121 +0,0 @@ -from django.contrib.admin.views.decorators import staff_member_required -from django.db.models import Count, F, Sum, Avg -from django.db.models.functions import ExtractYear, ExtractMonth -from django.http import JsonResponse -from django.shortcuts import render -from app.models import Purchase -from utils.charts import months, colorPrimary, colorSuccess, colorDanger, generate_color_palette, get_year_dict - - -@staff_member_required -def get_filter_options(request): - grouped_purchases = Purchase.objects.annotate(year=ExtractYear('time')).values('year').order_by('-year').distinct() - options = [purchase['year'] for purchase in grouped_purchases] - - return JsonResponse({ - 'options': options, - }) - - -@staff_member_required -def get_sales_chart(request, year): - purchases = Purchase.objects.filter(time__year=year) - grouped_purchases = purchases.annotate(price=F('item__price')).annotate(month=ExtractMonth('time'))\ - .values('month').annotate(average=Sum('item__price')).values('month', 'average').order_by('month') - - sales_dict = get_year_dict() - - for group in grouped_purchases: - sales_dict[months[group['month']-1]] = round(group['average'], 2) - - return JsonResponse({ - 'title': f'Sales in {year}', - 'data': { - 'labels': list(sales_dict.keys()), - 'datasets': [{ - 'label': 'Amount ($)', - 'backgroundColor': colorPrimary, - 'borderColor': colorPrimary, - 'data': list(sales_dict.values()), - }] - }, - }) - - -@staff_member_required -def spend_per_customer_chart(request, year): - purchases = Purchase.objects.filter(time__year=year) - grouped_purchases = purchases.annotate(price=F('item__price')).annotate(month=ExtractMonth('time'))\ - .values('month').annotate(average=Avg('item__price')).values('month', 'average').order_by('month') - - spend_per_customer_dict = get_year_dict() - - for group in grouped_purchases: - spend_per_customer_dict[months[group['month']-1]] = round(group['average'], 2) - - return JsonResponse({ - 'title': f'Spend per customer in {year}', - 'data': { - 'labels': list(spend_per_customer_dict.keys()), - 'datasets': [{ - 'label': 'Amount ($)', - 'backgroundColor': colorPrimary, - 'borderColor': colorPrimary, - 'data': list(spend_per_customer_dict.values()), - }] - }, - }) - - -@staff_member_required -def payment_success_chart(request, year): - purchases = Purchase.objects.filter(time__year=year) - - return JsonResponse({ - 'title': f'Payment success rate in {year}', - 'data': { - 'labels': ['Successful', 'Unsuccessful'], - 'datasets': [{ - 'label': 'Amount ($)', - 'backgroundColor': [colorSuccess, colorDanger], - 'borderColor': [colorSuccess, colorDanger], - 'data': [ - purchases.filter(successful=True).count(), - purchases.filter(successful=False).count(), - ], - }] - }, - }) - - -@staff_member_required -def payment_method_chart(request, year): - purchases = Purchase.objects.filter(time__year=year) - grouped_purchases = purchases.values('payment_method').annotate(count=Count('id'))\ - .values('payment_method', 'count').order_by('payment_method') - - payment_method_dict = dict() - - for payment_method in Purchase.PAYMENT_METHODS: - payment_method_dict[payment_method[1]] = 0 - - for group in grouped_purchases: - payment_method_dict[dict(Purchase.PAYMENT_METHODS)[group['payment_method']]] = group['count'] - - return JsonResponse({ - 'title': f'Payment method rate in {year}', - 'data': { - 'labels': list(payment_method_dict.keys()), - 'datasets': [{ - 'label': 'Amount ($)', - 'backgroundColor': generate_color_palette(len(payment_method_dict)), - 'borderColor': generate_color_palette(len(payment_method_dict)), - 'data': list(payment_method_dict.values()), - }] - }, - }) - - -@staff_member_required -def statistics_view(request): - return render(request, 'app/statistics.html', {}) \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 0fd64e2..bf656e4 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/frontend/__init__.py b/frontend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend/admin.py b/frontend/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/frontend/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/frontend/apps.py b/frontend/apps.py new file mode 100644 index 0000000..04f7b89 --- /dev/null +++ b/frontend/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FrontendConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'frontend' diff --git a/frontend/decorators.py b/frontend/decorators.py new file mode 100644 index 0000000..a88bac3 --- /dev/null +++ b/frontend/decorators.py @@ -0,0 +1,11 @@ +from django.http import HttpResponse +from django.shortcuts import redirect +# from django.db.models import Q + +def unauthenticated_user(view_func): + def wrapper_func(request, *args, **kwargs): + if request.user.is_authenticated: + return redirect('home') + else: + return view_func(request, *args, **kwargs) + return wrapper_func \ No newline at end of file diff --git a/frontend/migrations/__init__.py b/frontend/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend/models.py b/frontend/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/frontend/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/frontend/templates/accounts/login.html b/frontend/templates/accounts/login.html new file mode 100644 index 0000000..1131704 --- /dev/null +++ b/frontend/templates/accounts/login.html @@ -0,0 +1,98 @@ +{% load static %} + + + + + + + Sign in & Sign up Form + +{% block body %} + + + +
+
+ +
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/frontend/templates/frontend/accounts.html b/frontend/templates/frontend/accounts.html new file mode 100644 index 0000000..e69de29 diff --git a/app/templates/app/home.html b/frontend/templates/frontend/home.html similarity index 86% rename from app/templates/app/home.html rename to frontend/templates/frontend/home.html index 442b20f..5be6895 100644 --- a/app/templates/app/home.html +++ b/frontend/templates/frontend/home.html @@ -4,9 +4,10 @@ - BudMan - BudMan is a web app that helps you track and adjust your spending so that you are in control of money. - + BudMan - BudMan is a web app that helps you track and adjust your spending so that you are in control of money.

BudMan - BudMan is a web app that helps you track and adjust your spending so that you are in control of money.

+ Login + Logout \ No newline at end of file diff --git a/frontend/tests.py b/frontend/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/frontend/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/frontend/urls.py b/frontend/urls.py new file mode 100644 index 0000000..77319b6 --- /dev/null +++ b/frontend/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from .views import * +urlpatterns = [ + path('', home), + path('login', loginPage), + path('logout', logoutUser), +] \ No newline at end of file diff --git a/frontend/views.py b/frontend/views.py new file mode 100644 index 0000000..f46ccd4 --- /dev/null +++ b/frontend/views.py @@ -0,0 +1,40 @@ +from django.shortcuts import render +from django.http import JsonResponse +from .decorators import unauthenticated_user +from django.contrib.auth.forms import UserCreationForm + +def home(request): + return render(request, 'frontend/home.html') + +@unauthenticated_user +def loginPage(request): + form = UserCreationForm() + if request.method == 'POST': + if request.POST.get("form_type") == 'Sign up': + if request.method == 'POST' : + form = UserCreationForm(request.POST) + if form.is_valid(): + user = form.save() + user.save() + messages.success(request, 'Account created for '+ user.username) + return redirect('login') + + + elif request.POST.get("form_type") == 'Login': + username = request.POST.get('username') + password = request.POST.get('password') + + user = authenticate(request, username=username, password=password) + + if user is not None: + login(request, user) + return redirect('home') + else: + messages.info(request, 'Username OR password is incorrect') + + context = {'form':form} + return render (request, 'accounts/login.html', context) + +def logoutUser(request): + logout(request) + return redirect('login') \ No newline at end of file