Compare commits

..

4 Commits

Author SHA1 Message Date
cef423c3d1 Add latest Bill functions 2022-01-04 18:37:12 -05:00
f9af3cfefe Add Get Overdue Bills 2021-12-14 01:04:49 -05:00
20096010b4 Add Celery Support
This needs to be tested on a linux host - windows not supported
2021-12-13 15:21:44 -05:00
7b2f789724 Install Celery 2021-12-13 14:46:56 -05:00
85 changed files with 130 additions and 68393 deletions

View File

@ -1 +0,0 @@
3.9.0

20
.vscode/launch.json vendored
View File

@ -1,20 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/api/manage.py",
"args": [
"runserver",
"0.0.0.0:9000"
],
"django": true,
"justMyCode": true
}
]
}

View File

@ -16,8 +16,7 @@
"titleBar.activeBackground": "#f9e64f", "titleBar.activeBackground": "#f9e64f",
"titleBar.activeForeground": "#15202b", "titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#f9e64f99", "titleBar.inactiveBackground": "#f9e64f99",
"titleBar.inactiveForeground": "#15202b99", "titleBar.inactiveForeground": "#15202b99"
"commandCenter.border": "#15202b99"
}, },
"peacock.color": "#f9e64f" "peacock.color": "#f9e64f"
} }

View File

@ -5,36 +5,14 @@ The AHosking.com API
## Getting Started ## Getting Started
### ###
1. `sudo apt install libpq-dev python-dev python3-psycopg2` 1. `sudo apt install libpq-dev python-dev python3-psycopg2`
1. `pip install virtualenv` 1. `pip install virtualenv`
1. `python3 -m virtualenv .env` 1. `python3 -m virtualenv .env`
1. Activate environment 1. Activate environment
`.\.env\Scripts\activate` `.\.env\Scripts\activate`
1. `export DEBUG=false` 1.
1. `docker run --name api-postgres -e POSTGRES_PASSWORD=mysecretpassword -e POSTGRES_USER=api -e POSTGRES_DB=api -p 5432:5432 -d postgres`
1. ```
export DATABASE_HOST=localhost
export DATABASE_PORT=5432
export DATABASE_USER=api
export DATABASE_PASSWORD=mysecretpassword
export DATABASE_NAME=api
```
1. `python3 manage.py makemigrations`
1. `python3 manage.py migrate`
1. `python3 manage.py createsuperuser`
1. `python manage.py runserver 0.0.0.0:9000`
## Troubleshooting
`pip3 install --upgrade --force-reinstall -r requirements.txt` will re-install requirements and upgrade based on requirements.txt
### Third-party ### Third-party
- https://pypi.org/project/django-4-jet/ * https://pypi.org/project/django-3-jet/
## Themes and Templates
- https://startbootstrap.com/theme/landing-page
- https://startbootstrap.com/previews/new-age
- https://startbootstrap.com/theme/new-age

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts'

View File

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@ -0,0 +1,5 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)

23
api/api/celery.py Normal file
View File

@ -0,0 +1,23 @@
import os
from celery import Celery
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api.settings')
app = Celery('api')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django apps.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f'Request: {self.request!r}')

View File

@ -12,8 +12,6 @@ https://docs.djangoproject.com/en/3.2/ref/settings/
from pathlib import Path from pathlib import Path
import environ import environ
import os
import environ
env = environ.Env() env = environ.Env()
environ.Env.read_env() environ.Env.read_env()
@ -26,22 +24,20 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env( SECRET_KEY = env("SECRET_KEY", default='django-insecure-7uajhmbt^@)mklk1ur=slkmn3*+9_cnfhww6wi8jg*h@qqd%6u')
"SECRET_KEY", default='django-insecure-7uajhmbt^@)mklk1ur=slkmn3*+9_cnfhww6wi8jg*h@qqd%6u')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env("DEBUG") DEBUG = env("DEBUG")
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "192.168.1.187", ALLOWED_HOSTS = ["127.0.0.1", "192.168.1.187", "192.168.1.23"]
"192.168.1.23", "192.168.1.125", "0.0.0.0"]
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'bills', 'jet.dashboard',
# 'bills.apps.BillsConfig', 'jet',
'admin_soft.apps.AdminSoftDashboardConfig', 'bills.apps.BillsConfig',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
@ -51,7 +47,6 @@ INSTALLED_APPS = [
'rest_framework', 'rest_framework',
'rest_framework.authtoken', 'rest_framework.authtoken',
'django_filters', 'django_filters',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -69,7 +64,7 @@ ROOT_URLCONF = 'api.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'api/templates')], 'DIRS': [],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@ -134,7 +129,7 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/ # https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
@ -176,12 +171,6 @@ JET_THEMES = [
} }
] ]
# JET_INDEX_DASHBOARD = 'jet.dashboard.dashboard.DefaultIndexDashboard' JET_INDEX_DASHBOARD = 'jet.dashboard.dashboard.DefaultIndexDashboard'
# JET_APP_INDEX_DASHBOARD = 'dashboard.CustomIndexDashboard' JET_APP_INDEX_DASHBOARD = 'bills.dashboard.CustomIndexDashboard'
JET_INDEX_DASHBOARD = 'dashboard.CustomIndexDashboard' # JET_INDEX_DASHBOARD = 'bills.dashboard.CustomIndexDashboard'
STATICFILES_DIRS = [
BASE_DIR / "api/static",
("assets", BASE_DIR / "api/static/assets"),
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 939 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,488 +0,0 @@
/*!
* Bootstrap Reboot v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors
* Copyright 2011-2022 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-bg: #fff;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-2xl: 2rem;
--bs-border-radius-pill: 50rem;
--bs-link-color: #0d6efd;
--bs-link-hover-color: #0a58ca;
--bs-code-color: #d63384;
--bs-highlight-bg: #fff3cd;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: 1px solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: var(--bs-link-color);
text-decoration: underline;
}
a:hover {
color: var(--bs-link-hover-color);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,485 +0,0 @@
/*!
* Bootstrap Reboot v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors
* Copyright 2011-2022 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-bg: #fff;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-2xl: 2rem;
--bs-border-radius-pill: 50rem;
--bs-link-color: #0d6efd;
--bs-link-hover-color: #0a58ca;
--bs-code-color: #d63384;
--bs-highlight-bg: #fff3cd;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: 1px solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: var(--bs-link-color);
text-decoration: underline;
}
a:hover {
color: var(--bs-link-hover-color);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +0,0 @@
/*!
* Start Bootstrap - Landing Page v6.0.6 (https://startbootstrap.com/theme/landing-page)
* Copyright 2013-2023 Start Bootstrap
* Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-landing-page/blob/master/LICENSE)
*/
// This file is intentionally blank
// Use this file to add JavaScript to your project

View File

@ -1,14 +0,0 @@
<b>Bills!</b>
<ul>
<li><a href="{% url 'bills:create' %}">Create Bill</a></li>
</ul>
<hr>
{% if bills_list %}
<ul>
{% for bill in bills_list %}
<li><a href="{% url 'bills:detail' bill.id %}"> {{ bill.name }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No bills are available.</p>
{% endif %}

View File

@ -1,100 +0,0 @@
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap demo</title>
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet" />
</head>
<body>
<header class="p-3 text-bg-dark">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
<svg class="bi me-2" width="40" height="32" role="img" aria-label="Bootstrap"><use xlink:href="#bootstrap"></use></svg>
</a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a href="#" class="nav-link px-2 text-secondary">Home</a></li>
<li><a href="#" class="nav-link px-2 text-white">Features</a></li>
<li><a href="#" class="nav-link px-2 text-white">Pricing</a></li>
<li><a href="#" class="nav-link px-2 text-white">FAQs</a></li>
<li><a href="#" class="nav-link px-2 text-white">About</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search">
<input type="search" class="form-control form-control-dark text-bg-dark" placeholder="Search..." aria-label="Search">
</form>
<div class="text-end">
<button type="button" class="btn btn-outline-light me-2">Login</button>
<button type="button" class="btn btn-warning">Sign-up</button>
</div>
</div>
</div>
</header>
<script src="{% static 'js/bootstrap.bundle.min.js' %}" crossorigin="anonymous"></script>
<!-- end header -->
<div class="col-lg-8 mx-auto p-4 py-md-5">
<!-- main -->
<main>
<h1>Bills</h1>
<p class="fs-5 col-md-8">Quickly and easily start adding your bills and service costs to get a better understanding of your operational expenses.</p>
<div class="mb-5">
<a href="#" class="btn btn-primary btn-lg px-4">Sign-up!</a>
</div>
<hr class="col-3 col-md-2 mb-5">
<div class="row g-5">
<div class="col-md-6">
<h2>Starter projects</h2>
<p>Ready to beyond the starter template? Check out these open source projects that you can quickly duplicate to a new GitHub repository.</p>
<ul class="icon-list ps-0">
<li class="d-flex align-items-start mb-1"><a href="https://github.com/twbs/bootstrap-npm-starter" rel="noopener" target="_blank">Bootstrap npm starter</a></li>
<li class="text-muted d-flex align-items-start mb-1">Bootstrap Parcel starter (coming soon!)</li>
</ul>
</div>
<div class="col-md-6">
<h2>Guides</h2>
<p>Read more detailed instructions and documentation on using or contributing to Bootstrap.</p>
<ul class="icon-list ps-0">
<li class="d-flex align-items-start mb-1"><a href="../getting-started/introduction/">Bootstrap quick start guide</a></li>
<li class="d-flex align-items-start mb-1"><a href="../getting-started/webpack/">Bootstrap Webpack guide</a></li>
<li class="d-flex align-items-start mb-1"><a href="../getting-started/parcel/">Bootstrap Parcel guide</a></li>
<li class="d-flex align-items-start mb-1"><a href="../getting-started/vite/">Bootstrap Vite guide</a></li>
<li class="d-flex align-items-start mb-1"><a href="../getting-started/contribute/">Contributing to Bootstrap</a></li>
</ul>
</div>
</div>
</main>
<!-- end main -->
</div>
<!-- footer -->
<div class="container">
<footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top">
<p class="col-md-4 mb-0 text-muted">© 2022 Automated Bytes Inc.</p>
<a href="/" class="col-md-4 d-flex align-items-center justify-content-center mb-3 mb-md-0 me-md-auto link-dark text-decoration-none">
<svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"></use></svg>
</a>
<ul class="nav col-md-4 justify-content-end">
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Home</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Features</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">Pricing</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">FAQs</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">About</a></li>
</ul>
</footer>
</div>
</body>
</html>

View File

@ -13,7 +13,7 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.urls import path from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.urls import include, path from django.urls import include, path
@ -24,6 +24,10 @@ urlpatterns = [
# url(r'^$', view=TemplateView.as_view(template_name='bills/home.html')), # url(r'^$', view=TemplateView.as_view(template_name='bills/home.html')),
# url(r'^$', view=TemplateView.as_view(template_name='main_api/home.html')), # url(r'^$', view=TemplateView.as_view(template_name='main_api/home.html')),
path('', views.index, name='index'), path('', views.index, name='index'),
# url(r'^jet/', include('jet.urls', 'jet')), # Django JET URLS
# url(r'^jet/dashboard/', include('jet.dashboard.urls', 'jet-dashboard')),
path('jet/', include('jet.urls', 'jet')),
path('jet/dashboard', include('jet.dashboard.urls', 'jet-dashboard')),
path('bills/', include('bills.urls')), path('bills/', include('bills.urls')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
] ]

View File

@ -1,16 +1,4 @@
from django.contrib.auth import logout
from django.http import HttpResponse from django.http import HttpResponse
from django.template import loader
from django.shortcuts import redirect
def index(request): def index(request):
# return HttpResponse("Hi there! This is the API index") return HttpResponse("Hi there! This is the API index")
template = loader.get_template('index.html')
context = {}
return HttpResponse(template.render(context, request))
def logout(request):
logout(request)
redirect(index)

View File

@ -1,21 +1,32 @@
from django.contrib import admin from django.contrib import admin
import pendulum
from .models import Bill, Organization from .models import Bill
from.tasks import get_overdue_bills, get_upcoming_bills
@admin.action(description='Duplicate Bill')
def duplicate(modeladmin, request, queryset):
for object in queryset:
object.id = None
# object.name = object.name+'-duplicate' ## This was temporary while I figured out how to massage the date
# print(object.due)
new_date = (pendulum.parse(str(object.due), exact=True)).add(months=1)
# print(new_date)
object.due = new_date
object.save()
@admin.action(description='Duplicate Older Bill')
def duplicate_old(modeladmin, request, queryset):
for object in queryset:
object.id = None
new_date = (pendulum.parse(str(object.due), exact=True)).subtract(months=1)
object.due = new_date
object.save()
@admin.register(Bill) @admin.register(Bill)
class BillAdmin(admin.ModelAdmin): class BillAdmin(admin.ModelAdmin):
list_display = ['name', 'due', 'amount'] list_display = ['name', 'due', 'amount']
list_filter = ('name', 'type', 'is_paid', 'is_overdue', 'is_missed') list_filter = ('name', 'type', 'is_paid', 'is_overdue', 'is_missed')
search_fields = ['name', 'type', 'amount'] search_fields = ['name', 'type', 'amount'
]
actions = [duplicate, duplicate_old, get_overdue_bills, get_upcoming_bills]
@admin.register(Organization)
class Organization(admin.ModelAdmin):
list_display = ['name']
search_fields = ['name']
list_filter = ('name',)
ordering = ['name']
# prepopulated_fields = {'slug': ('name',)}
# raw_id_fields = ('members',)
# readonly_fields = ('members',)

View File

@ -2,10 +2,7 @@ from django.utils.translation import ugettext_lazy as _
from jet.dashboard import modules from jet.dashboard import modules
from jet.dashboard.dashboard import Dashboard, AppIndexDashboard from jet.dashboard.dashboard import Dashboard, AppIndexDashboard
class CustomIndexDashboard(Dashboard): class CustomIndexDashboard(Dashboard):
# app_label = "api"
columns = 3 columns = 3
def init_with_context(self, context): def init_with_context(self, context):
@ -28,7 +25,19 @@ class CustomIndexDashboard(Dashboard):
'url': 'irc://irc.freenode.net/django', 'url': 'irc://irc.freenode.net/django',
'external': True, 'external': True,
}, },
{
'title': _('COME ON'),
'url': 'irc://irc.freenode.net/django',
'external': True,
},
], ],
column=0, column=0,
order=0 order=0
)) ))
self.children.append(modules.ModelList(
_('Models'),
exclude=('auth.*',),
column=0,
order=0
))

View File

@ -1,24 +0,0 @@
# Generated by Django 4.1.7 on 2023-04-08 15:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bills', '0004_auto_20211203_1549'),
]
operations = [
migrations.CreateModel(
name='Organization',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, verbose_name='Name')),
('address', models.CharField(max_length=64, verbose_name='Address')),
('phone', models.CharField(max_length=64, verbose_name='Phone')),
('email', models.CharField(max_length=64, verbose_name='Email')),
('website', models.CharField(max_length=64, verbose_name='Website')),
],
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 4.1.7 on 2023-04-08 15:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('bills', '0005_organization'),
]
operations = [
migrations.AddField(
model_name='bill',
name='organization',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='bills.organization', verbose_name='Organization'),
),
]

View File

@ -1,36 +1,18 @@
from django.db import models from django.db import models
# Create your models here. # Create your models here.
class Bill(models.Model): class Bill(models.Model):
name = models.CharField(max_length=64, verbose_name='Name') name = models.CharField(max_length=64, verbose_name='Name')
type = models.CharField(max_length=64, verbose_name="Type") type = models.CharField(max_length=64, verbose_name="Type")
due = models.DateField(verbose_name="Due Date") due = models.DateField(verbose_name="Due Date")
amount = models.FloatField(default='00.00') amount = models.FloatField(default='00.00')
is_paid = models.BooleanField(default=False, verbose_name="Paid") is_paid = models.BooleanField(default=False, verbose_name="Paid")
paid_date = models.DateField('Paid Date', null=True, blank=True) paid_date = models.DateField('Paid Date',null=True, blank=True)
is_overdue = models.BooleanField(default=False, verbose_name="Overdue") is_overdue = models.BooleanField(default=False, verbose_name="Overdue")
is_missed = models.BooleanField( is_missed = models.BooleanField(default=False, verbose_name='Missed Payment')
default=False, verbose_name='Missed Payment')
organization = models.ForeignKey(
'Organization', on_delete=models.CASCADE, verbose_name='Organization', null=True, blank=True)
def overdue(self): def overdue(self):
return self.is_overdue return self.is_overdue
def __str__(self): def __str__(self):
return ("%s - %s" % (self.name, self.due)) return ("%s - %s" % (self.name, self.due))
# Create organization model
class Organization(models.Model):
name = models.CharField(max_length=64, verbose_name='Name')
address = models.CharField(max_length=64, verbose_name='Address')
phone = models.CharField(max_length=64, verbose_name='Phone')
email = models.CharField(max_length=64, verbose_name='Email')
website = models.CharField(max_length=64, verbose_name='Website')
def __str__(self):
return ("%s - %s" % (self.name, self.phone))

35
api/bills/tasks.py Normal file
View File

@ -0,0 +1,35 @@
### Celery Tasks!
from django.core.checks import messages
import pendulum
from .models import Bill
from celery import shared_task
@shared_task
def get_overdue_bills(modeladmin, request, queryset):
try:
bills_list = []
bill_request = Bill.objects.filter(is_overdue=True)
print(bill_request)
for bill in bill_request:
# print(bill.id)
bills_list.append(bill.id)
except Bill.DoesNotExist:
bills_list = "There are no bills that are overdue!"
print("function complete")
print(bills_list)
return('This is a test')
@shared_task
def get_upcoming_bills(modeladmin, request, queryset):
today = pendulum.today().add(days=7)
print(today)
try:
bill_request = Bill.objects.filter(is_paid=False)
print(bill_request)
except Bill.DoesNotExist:
message = 'There are no bill coming due soon that are unpaid.'
print(message)
return(message)

View File

@ -1,10 +0,0 @@
<form action="{% url 'bills:create' %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>Create a New Bill</h1></legend>
<label for="name">Bill Name:</label>
<input type="text" name="name" id="name" value="">
<br>
</fieldset>
<input type="submit" value="Create Bill">
</form>

View File

@ -1,17 +0,0 @@
<h1> {{bill.name }}</h1>
{{ bill.due}}
<br>
{{ bill.type }}
<br>
{{ bill.amount}}
<br>
{{ bill.is_paid}}
<br>
{{ bill.paid_date}}
<br>
{{ bill.is_overdue}}
<br>
{{ bill.is_missed}}
<br>
{{ bill.organization.name}}

View File

@ -1,14 +0,0 @@
<b>Bills!</b>
<ul>
<li><a href="{% url 'bills:create' %}">Create Bill</a></li>
</ul>
<hr>
{% if bills_list %}
<ul>
{% for bill in bills_list %}
<li><a href="{% url 'bills:detail' bill.id %}"> {{ bill.name }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No bills are available.</p>
{% endif %}

View File

@ -1,9 +1,3 @@
from django.test import TestCase from django.test import TestCase
# Create your tests here. # Create your tests here.
from .models import Bill, Organization
class BillModelTests(TestCase):
def test_has_due_date_no_amount(self):
pass

View File

@ -1,15 +1,12 @@
from django.urls import path from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.urls import include, path from django.urls import include, path
from . import views from . import views
app_name = 'bills'
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path('', views.index, name='index'),
path('<int:bill_id>/', views.detail, name='detail'), path('admin/', admin.site.urls),
path('create', views.create, name='create')
# path('admin/', admin.site.urls),
] ]

View File

@ -1,32 +1,6 @@
# from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth import authenticate, login from django.http import HttpResponse
from django.http import HttpResponse, Http404
from django.template import loader
from django.shortcuts import render, get_object_or_404, get_list_or_404, redirect
from .models import Bill
def index(request): def index(request):
if request.user.is_authenticated: return HttpResponse("Let there be bills!")
bills_list = Bill.objects.order_by('-due') # Create your views here.
# bills_list = get_list_or_404(Bill)
template = loader.get_template('bills/index.html')
context = {'bills_list': bills_list, }
print(bills_list)
return HttpResponse(template.render(context, request))
else:
# return redirect(api.index)
return render(request, "index.html")
# return render(request, 'bills/index.html', context)
def detail(request, bill_id):
bill = get_object_or_404(Bill, pk=bill_id)
return render(request, 'bills/detail.html', {'bill': bill})
# view to retriev a users bills
def create(request):
return render(request, 'bills/create.html')

Binary file not shown.