Compare commits

..

12 Commits

Author SHA1 Message Date
ahosking
24cdebe960 Add templates! 2023-05-06 11:23:21 -04:00
ahosking
1edd0892af Add Static Assets 2023-05-06 11:22:42 -04:00
ahosking
d56b5725a0 Start Accounts application 2023-05-06 11:22:11 -04:00
ahosking
3b05d0983a Add Debugger config for VS code 2023-04-17 09:35:22 -04:00
ahosking
9d4ce6c413 Switch to Soft-UI admin from Jet
This also adds a bunch of templates and URLS
Start to add routes for user-facing bills pages

Add an Index page that may start to display some great work
2023-04-17 09:35:06 -04:00
ahosking
d0d25ef499 Add Theme Updates 2023-04-17 09:32:36 -04:00
ahosking
832e39fcd7 Chore: Update URLS
This is required to run under python3.11 and Django4.1
2023-03-25 11:52:25 -04:00
ahosking
1dd1188bdc Chore: Add localhost to allowed hosts 2023-03-25 11:51:58 -04:00
ahosking
cd2e4fca26 Chore: Add Developer Documentation
Add Instructions for getting started from scratch
Add command to create/run a database for dev/testing
Specify Python version for pyenv
Update requirements for nev versions of packages
2023-03-25 11:51:36 -04:00
ahosking
9930bec65a Add Operating system requirements 2023-01-19 15:23:12 -05:00
6ba83ac2d3 Add better docs and allowed hosts 2022-05-31 23:07:03 -04:00
Alexander Hosking
ebb48c1a42 Adding functions for bills 2021-12-25 22:04:59 -05:00
85 changed files with 68393 additions and 130 deletions

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.9.0

20
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
// 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,7 +16,8 @@
"titleBar.activeBackground": "#f9e64f",
"titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#f9e64f99",
"titleBar.inactiveForeground": "#15202b99"
"titleBar.inactiveForeground": "#15202b99",
"commandCenter.border": "#15202b99"
},
"peacock.color": "#f9e64f"
}

View File

@ -5,14 +5,36 @@ The AHosking.com API
## Getting Started
###
1. `sudo apt install libpq-dev python-dev python3-psycopg2`
1. `pip install virtualenv`
1. `python3 -m virtualenv .env`
1. Activate environment
`.\.env\Scripts\activate`
1.
`.\.env\Scripts\activate`
1. `export DEBUG=false`
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
* https://pypi.org/project/django-3-jet/
- https://pypi.org/project/django-4-jet/
## Themes and Templates
- https://startbootstrap.com/theme/landing-page
- https://startbootstrap.com/previews/new-age
- https://startbootstrap.com/theme/new-age

0
api/accounts/__init__.py Normal file
View File

3
api/accounts/admin.py Normal file
View File

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

6
api/accounts/apps.py Normal file
View File

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

View File

3
api/accounts/models.py Normal file
View File

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

3
api/accounts/tests.py Normal file
View File

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

3
api/accounts/views.py Normal file
View File

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

View File

@ -1,5 +0,0 @@
# 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',)

View File

@ -1,23 +0,0 @@
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

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

4124
api/api/static/css/bootstrap-grid.css vendored Normal file

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

4123
api/api/static/css/bootstrap-grid.rtl.css vendored Normal file

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

488
api/api/static/css/bootstrap-reboot.css vendored Normal file
View File

@ -0,0 +1,488 @@
/*!
* 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

@ -0,0 +1,485 @@
/*!
* 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

4266
api/api/static/css/bootstrap-utilities.css vendored Normal file

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

10878
api/api/static/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

7
api/api/static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

10842
api/api/static/css/bootstrap.rtl.css vendored Normal file

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

10919
api/api/static/css/styles.css Normal file

File diff suppressed because it is too large Load Diff

7075
api/api/static/js/bootstrap.bundle.js vendored Normal file

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

5202
api/api/static/js/bootstrap.esm.js vendored Normal file

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

5249
api/api/static/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

7
api/api/static/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
/*!
* 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

@ -0,0 +1,14 @@
<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

@ -0,0 +1,100 @@
{% 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
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.urls import path
from django.contrib import admin
from django.views.generic import TemplateView
from django.urls import include, path
@ -24,10 +24,6 @@ urlpatterns = [
# url(r'^$', view=TemplateView.as_view(template_name='bills/home.html')),
# url(r'^$', view=TemplateView.as_view(template_name='main_api/home.html')),
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('admin/', admin.site.urls),
]

View File

@ -1,4 +1,16 @@
from django.contrib.auth import logout
from django.http import HttpResponse
from django.template import loader
from django.shortcuts import redirect
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,32 +1,21 @@
from django.contrib import admin
import pendulum
from .models import Bill
from.tasks import get_overdue_bills, get_upcoming_bills
from .models import Bill, Organization
@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)
class BillAdmin(admin.ModelAdmin):
list_display = ['name', 'due', 'amount']
list_filter = ('name', 'type', 'is_paid', 'is_overdue', 'is_missed')
search_fields = ['name', 'type', 'amount'
]
actions = [duplicate, duplicate_old, get_overdue_bills, get_upcoming_bills]
search_fields = ['name', 'type', 'amount']
@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

@ -0,0 +1,24 @@
# 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

@ -0,0 +1,19 @@
# 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,18 +1,36 @@
from django.db import models
# Create your models here.
class Bill(models.Model):
name = models.CharField(max_length=64, verbose_name='Name')
type = models.CharField(max_length=64, verbose_name="Type")
due = models.DateField(verbose_name="Due Date")
amount = models.FloatField(default='00.00')
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_missed = models.BooleanField(default=False, verbose_name='Missed Payment')
is_missed = models.BooleanField(
default=False, verbose_name='Missed Payment')
organization = models.ForeignKey(
'Organization', on_delete=models.CASCADE, verbose_name='Organization', null=True, blank=True)
def overdue(self):
return self.is_overdue
def __str__(self):
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))

View File

@ -1,35 +0,0 @@
### 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

@ -0,0 +1,10 @@
<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

@ -0,0 +1,17 @@
<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

@ -0,0 +1,14 @@
<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,3 +1,9 @@
from django.test import TestCase
# Create your tests here.
from .models import Bill, Organization
class BillModelTests(TestCase):
def test_has_due_date_no_amount(self):
pass

View File

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

View File

@ -1,6 +1,32 @@
from django.shortcuts import render
from django.http import HttpResponse
# from django.shortcuts import render
from django.contrib.auth import authenticate, login
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):
return HttpResponse("Let there be bills!")
# Create your views here.
if request.user.is_authenticated:
bills_list = Bill.objects.order_by('-due')
# 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.