bootstrap
Pete Ley 1 year ago
commit 5f53e16dec

3
.gitignore vendored

@ -0,0 +1,3 @@
db.sqlite3
venv/
__pycache__/

@ -0,0 +1,4 @@
rm db.sqlite3 jobs/migrations/00*
python manage.py makemigrations
python manage.py migrate
python manage.py loaddata data.yaml

@ -0,0 +1,691 @@
- model: jobs.address
pk: 1
fields:
name: Jobsite
street_address: 705 Pike St
street_address2: ''
city: Seattle
state: WA
zip_code: '98101'
- model: jobs.address
pk: 2
fields:
name: American Bridge Company
street_address: 1390 Willow Pass Road
street_address2: ''
city: Concord
state: CA
zip_code: '94520'
- model: jobs.address
pk: 3
fields:
name: American Bridge Kenmore Yard
street_address: 6423 NE 175th St
street_address2: ''
city: Kenmore
state: WA
zip_code: '98028'
- model: jobs.address
pk: 4
fields:
name: American Bridge New Snohomish Yard
street_address: 9525 Airport Way
street_address2: ''
city: Snohomish
state: WA
zip_code: '98296'
- model: jobs.address
pk: 5
fields:
name: American Bridge Office
street_address: 1100 Olive Way, Suite 940
street_address2: ''
city: Seattle
state: WA
zip_code: '98401'
- model: jobs.address
pk: 6
fields:
name: Jobsite
street_address: 277 27th Street
street_address2: ''
city: Oakland
state: CA
zip_code: '94612'
- model: jobs.company
pk: 1
fields:
name: Howard S Wright
- model: jobs.company
pk: 2
fields:
name: GBD Architects, Inc
- model: jobs.company
pk: 3
fields:
name: KPFF
- model: jobs.company
pk: 4
fields:
name: McGinnis Structural
- model: jobs.company
pk: 5
fields:
name: Clark / Lease Crutcher Lewis Joint Venture
- model: jobs.company
pk: 6
fields:
name: LMN Architects
- model: jobs.company
pk: 7
fields:
name: Magnussen Klemencic Associates
- model: jobs.company
pk: 8
fields:
name: American Bridge Company
- model: jobs.company
pk: 9
fields:
name: Alliance Engineering
- model: jobs.company
pk: 10
fields:
name: Holland Partner Group
- model: jobs.company
pk: 11
fields:
name: HKS Architects
- model: jobs.contact
pk: 1
fields:
name: Brian Petersen
email: bpetersen@americanbridge.net
company: 8
- model: jobs.contact
pk: 2
fields:
name: Paul Prowant
email: ''
company: 8
- model: jobs.contact
pk: 3
fields:
name: Ben Mueller
email: ''
company: 8
- model: jobs.detailer
pk: 1
fields:
name: Peter Ley
email: pley@pacificstair.com
- model: jobs.detailer
pk: 2
fields:
name: Matt Walz
email: mwalz@pacificstair.com
- model: jobs.detailer
pk: 3
fields:
name: Craig Graham
email: cgraham@pacificstair.com
- model: jobs.detailer
pk: 4
fields:
name: Nick Reutzel
email: nreutzel@pacificstair.com
- model: jobs.detailer
pk: 5
fields:
name: Joe Handy
email: jhandy@pacificstair.com
- model: jobs.detailer
pk: 6
fields:
name: Austin Lee
email: alee@pacificstair.com
- model: jobs.detailer
pk: 7
fields:
name: Carl Markley
email: cmarkley@pacificstair.com
- model: jobs.detailer
pk: 8
fields:
name: Brooke Wren
email: bwren@pacificstair.com
- model: jobs.detailer
pk: 9
fields:
name: Tyler Nunn
email: tnunn@pacificstair.com
- model: jobs.detailer
pk: 10
fields:
name: Ray Miller
email: rmiller@pacificstair.com
- model: jobs.detailer
pk: 11
fields:
name: Dennis Dreischmeyer
email: dennisd@pacificstair.com
- model: jobs.detailer
pk: 12
fields:
name: Megan Nyburg
email: mnyburg@pacificstair.com
- model: jobs.detailer
pk: 13
fields:
name: Dolan Classen
email: ''
- model: jobs.projectmanager
pk: 1
fields:
name: Scott Mack
email: smack@pacificstair.com
- model: jobs.projectmanager
pk: 2
fields:
name: Tyler Gardner
email: tyler@pacificstair.com
- model: jobs.projectmanager
pk: 3
fields:
name: Ken Stodola
email: kstodola@pacificstair.com
- model: jobs.projectmanager
pk: 4
fields:
name: Jason Mowery
email: jmowery@pacificstair.com
- model: jobs.projectmanager
pk: 5
fields:
name: Tracey Goettsch
email: tracy@pacificstair.com
- model: jobs.job
pk: 1290
fields:
name: Washington State Convention Center Addition
contractor: 5
architect: 6
eor: 7
customer: 8
project_manager: 1
award_date: 2018-03-01
modeling: true
model_upload: ''
addresses:
- 2
- 3
- 1
- 4
- 5
contacts:
- 3
- 1
- 2
sub_detailers:
- 7
fab_detailers:
- 6
engineers:
- 9
- 4
installers: []
- model: jobs.job
pk: 1360
fields:
name: 24th & Harrison
contractor: 10
architect: 11
eor: 7
customer: 10
project_manager: 5
award_date: 2019-03-13
modeling: false
model_upload: ''
addresses:
- 6
contacts: []
sub_detailers:
- 12
fab_detailers:
- 13
engineers:
- 9
installers: []
- model: jobs.submittal
pk: 1
fields:
job: 1290
number: 1
- model: jobs.submittal
pk: 2
fields:
job: 1360
number: 1
- model: auth.permission
pk: 1
fields:
name: Can add address
content_type: 1
codename: add_address
- model: auth.permission
pk: 2
fields:
name: Can change address
content_type: 1
codename: change_address
- model: auth.permission
pk: 3
fields:
name: Can delete address
content_type: 1
codename: delete_address
- model: auth.permission
pk: 4
fields:
name: Can view address
content_type: 1
codename: view_address
- model: auth.permission
pk: 5
fields:
name: Can add company
content_type: 2
codename: add_company
- model: auth.permission
pk: 6
fields:
name: Can change company
content_type: 2
codename: change_company
- model: auth.permission
pk: 7
fields:
name: Can delete company
content_type: 2
codename: delete_company
- model: auth.permission
pk: 8
fields:
name: Can view company
content_type: 2
codename: view_company
- model: auth.permission
pk: 9
fields:
name: Can add contact
content_type: 3
codename: add_contact
- model: auth.permission
pk: 10
fields:
name: Can change contact
content_type: 3
codename: change_contact
- model: auth.permission
pk: 11
fields:
name: Can delete contact
content_type: 3
codename: delete_contact
- model: auth.permission
pk: 12
fields:
name: Can view contact
content_type: 3
codename: view_contact
- model: auth.permission
pk: 13
fields:
name: Can add detailer
content_type: 4
codename: add_detailer
- model: auth.permission
pk: 14
fields:
name: Can change detailer
content_type: 4
codename: change_detailer
- model: auth.permission
pk: 15
fields:
name: Can delete detailer
content_type: 4
codename: delete_detailer
- model: auth.permission
pk: 16
fields:
name: Can view detailer
content_type: 4
codename: view_detailer
- model: auth.permission
pk: 17
fields:
name: Can add job
content_type: 5
codename: add_job
- model: auth.permission
pk: 18
fields:
name: Can change job
content_type: 5
codename: change_job
- model: auth.permission
pk: 19
fields:
name: Can delete job
content_type: 5
codename: delete_job
- model: auth.permission
pk: 20
fields:
name: Can view job
content_type: 5
codename: view_job
- model: auth.permission
pk: 21
fields:
name: Can add project manager
content_type: 6
codename: add_projectmanager
- model: auth.permission
pk: 22
fields:
name: Can change project manager
content_type: 6
codename: change_projectmanager
- model: auth.permission
pk: 23
fields:
name: Can delete project manager
content_type: 6
codename: delete_projectmanager
- model: auth.permission
pk: 24
fields:
name: Can view project manager
content_type: 6
codename: view_projectmanager
- model: auth.permission
pk: 25
fields:
name: Can add stair
content_type: 7
codename: add_stair
- model: auth.permission
pk: 26
fields:
name: Can change stair
content_type: 7
codename: change_stair
- model: auth.permission
pk: 27
fields:
name: Can delete stair
content_type: 7
codename: delete_stair
- model: auth.permission
pk: 28
fields:
name: Can view stair
content_type: 7
codename: view_stair
- model: auth.permission
pk: 29
fields:
name: Can add submittal
content_type: 8
codename: add_submittal
- model: auth.permission
pk: 30
fields:
name: Can change submittal
content_type: 8
codename: change_submittal
- model: auth.permission
pk: 31
fields:
name: Can delete submittal
content_type: 8
codename: delete_submittal
- model: auth.permission
pk: 32
fields:
name: Can view submittal
content_type: 8
codename: view_submittal
- model: auth.permission
pk: 33
fields:
name: Can add stair revision
content_type: 9
codename: add_stairrevision
- model: auth.permission
pk: 34
fields:
name: Can change stair revision
content_type: 9
codename: change_stairrevision
- model: auth.permission
pk: 35
fields:
name: Can delete stair revision
content_type: 9
codename: delete_stairrevision
- model: auth.permission
pk: 36
fields:
name: Can view stair revision
content_type: 9
codename: view_stairrevision
- model: auth.permission
pk: 37
fields:
name: Can add job note
content_type: 10
codename: add_jobnote
- model: auth.permission
pk: 38
fields:
name: Can change job note
content_type: 10
codename: change_jobnote
- model: auth.permission
pk: 39
fields:
name: Can delete job note
content_type: 10
codename: delete_jobnote
- model: auth.permission
pk: 40
fields:
name: Can view job note
content_type: 10
codename: view_jobnote
- model: auth.permission
pk: 41
fields:
name: Can add log entry
content_type: 11
codename: add_logentry
- model: auth.permission
pk: 42
fields:
name: Can change log entry
content_type: 11
codename: change_logentry
- model: auth.permission
pk: 43
fields:
name: Can delete log entry
content_type: 11
codename: delete_logentry
- model: auth.permission
pk: 44
fields:
name: Can view log entry
content_type: 11
codename: view_logentry
- model: auth.permission
pk: 45
fields:
name: Can add permission
content_type: 12
codename: add_permission
- model: auth.permission
pk: 46
fields:
name: Can change permission
content_type: 12
codename: change_permission
- model: auth.permission
pk: 47
fields:
name: Can delete permission
content_type: 12
codename: delete_permission
- model: auth.permission
pk: 48
fields:
name: Can view permission
content_type: 12
codename: view_permission
- model: auth.permission
pk: 49
fields:
name: Can add group
content_type: 13
codename: add_group
- model: auth.permission
pk: 50
fields:
name: Can change group
content_type: 13
codename: change_group
- model: auth.permission
pk: 51
fields:
name: Can delete group
content_type: 13
codename: delete_group
- model: auth.permission
pk: 52
fields:
name: Can view group
content_type: 13
codename: view_group
- model: auth.permission
pk: 53
fields:
name: Can add user
content_type: 14
codename: add_user
- model: auth.permission
pk: 54
fields:
name: Can change user
content_type: 14
codename: change_user
- model: auth.permission
pk: 55
fields:
name: Can delete user
content_type: 14
codename: delete_user
- model: auth.permission
pk: 56
fields:
name: Can view user
content_type: 14
codename: view_user
- model: auth.permission
pk: 57
fields:
name: Can add content type
content_type: 15
codename: add_contenttype
- model: auth.permission
pk: 58
fields:
name: Can change content type
content_type: 15
codename: change_contenttype
- model: auth.permission
pk: 59
fields:
name: Can delete content type
content_type: 15
codename: delete_contenttype
- model: auth.permission
pk: 60
fields:
name: Can view content type
content_type: 15
codename: view_contenttype
- model: auth.permission
pk: 61
fields:
name: Can add session
content_type: 16
codename: add_session
- model: auth.permission
pk: 62
fields:
name: Can change session
content_type: 16
codename: change_session
- model: auth.permission
pk: 63
fields:
name: Can delete session
content_type: 16
codename: delete_session
- model: auth.permission
pk: 64
fields:
name: Can view session
content_type: 16
codename: view_session
- model: auth.permission
pk: 65
fields:
name: Can add session
content_type: 17
codename: add_session
- model: auth.permission
pk: 66
fields:
name: Can change session
content_type: 17
codename: change_session
- model: auth.permission
pk: 67
fields:
name: Can delete session
content_type: 17
codename: delete_session
- model: auth.permission
pk: 68
fields:
name: Can view session
content_type: 17
codename: view_session
- model: auth.user
pk: 1
fields:
password: pbkdf2_sha256$390000$WCTvDu5CuzaHt3ZHCDHJ4p$YoRBlufp9vIo6EXPymvSijAIrF/9ZD5WUR23hOoUL8s=
last_login: 2022-12-29 17:47:01.105541+00:00
is_superuser: true
username: brian
first_name: ''
last_name: ''
email: bhendricks@pacificstair.com
is_staff: true
is_active: true
date_joined: 2022-12-29 17:21:56.918033+00:00
groups: []
user_permissions: []

@ -0,0 +1,16 @@
"""
ASGI config for dwgstatus project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dwgstatus.settings")
application = get_asgi_application()

@ -0,0 +1,125 @@
"""
Django settings for dwgstatus project.
Generated by 'django-admin startproject' using Django 4.1.4.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-%q_1-ql04zin3dy32cx@gtj_)onxaa1fm9wvvmf+2qhwyz4_&r"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'jobs',
"django.contrib.admin",
'django.contrib.admindocs',
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "dwgstatus.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "dwgstatus.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "America/Los_Angeles"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

@ -0,0 +1,22 @@
"""dwgstatus URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
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.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('jobs.urls')),
path("admin/", admin.site.urls),
]

@ -0,0 +1,16 @@
"""
WSGI config for dwgstatus project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dwgstatus.settings")
application = get_wsgi_application()

@ -0,0 +1,194 @@
from django.contrib import admin
from django.forms.models import BaseInlineFormSet
from django import forms
from .models import (
Address,
Job,
JobNote,
Submittal,
Stair,
StairRevision,
Company,
Detailer,
ProjectManager,
Contact,
)
admin.site.site_header = 'Drawing Status admin'
admin.site.site_title = 'Drawing Status'
class SubInline(admin.TabularInline):
show_change_link = True
model = Submittal
extra = 1
class JobNoteInline(admin.TabularInline):
model = JobNote
extra = 0
@admin.register(Job)
class JobAdmin(admin.ModelAdmin):
list_display = ['number', 'name']
list_display_links = ['name']
list_filter = [
'addresses__state',
]
search_fields = ['number', 'name', 'addresses']
inlines = [SubInline, JobNoteInline]
fieldsets = [
(None, {
'fields': [
('number',
'name',
'award_date',),
'addresses',
]
}),
('Client Info', {
'fields': [
'contractor',
'architect',
'eor',
'customer',
'contacts',
]
}),
('In-house Info', {
'fields': [
'project_manager',
('sub_detailers',
'fab_detailers',),
'engineers',
'installers',
]
}),
('Options', {
'fields': [
'modeling',
'model_upload',
]
}),
]
autocomplete_fields = [
'addresses',
'contractor',
'architect',
'eor',
'customer',
'contacts',
'engineers',
'installers',
]
class CloneInlineFormset(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
form.fields["CLONE"] = forms.BooleanField(required=False)
class StackedInlineWithClone(admin.StackedInline):
template = "admin/edit_inline/stacked_with_clone.html"
formset = CloneInlineFormset
class StairInline(StackedInlineWithClone):
model = Stair
extra = 0
show_change_link = True
fieldsets = [
(None, {'fields': [
('name', 'grid', 'levels', 'stn',),
]}),
('Flights', {'fields': [
'flights',
('stringer_type', 'stringer_custom'),
('stair_con', 'stair_con_custom',),
'stair_qt',
]}),
('Landings', {'fields': [
'landings',
('land_con', 'land_con_custom',),
'land_qt',
]}),
('Rails', {'fields': [
('rail_type', 'rail_type_custom'),
]}),
]
class CloneStairInlineMixin():
def save_formset(self, request, form, formset, change):
ret = super().save_formset(request, form, formset, change)
stairs_to_clone = []
for data in formset.cleaned_data:
if isinstance(data.get('id'), Stair):
if data.get('CLONE'):
stairs_to_clone.append(data.get('id').id)
for stair in Stair.objects.filter(id__in=stairs_to_clone):
stair.id = None
stair.save()
return ret
@admin.register(Submittal)
class SubmittalAdmin(admin.ModelAdmin, CloneStairInlineMixin):
ordering = ['job__number', 'number']
inlines = [StairInline]
search_fields = ['job__number', 'job__name']
autocomplete_fields = ['job']
class StairRevInline(admin.StackedInline):
model = StairRevision
extra = 0
@admin.register(Stair)
class StairAdmin(admin.ModelAdmin):
ordering = ['submittal__job__number', 'submittal__number', 'name']
autocomplete_fields = ['submittal']
search_fields = ['submittal__job__number', 'submittal__job__name']
inlines = [StairRevInline]
class Named(admin.ModelAdmin):
search_fields = ['name']
@admin.register(Contact)
class ContactAdmin(Named):
autocomplete_fields = ['company']
admin.site.register(Company, Named)
admin.site.register(Detailer, Named)
admin.site.register(ProjectManager, Named)
@admin.register(Address)
class AddressAdmin(admin.ModelAdmin):
list_display = [
'name',
'street_address',
'street_address2',
'city',
'state',
'zip_code',
]
search_fields = [
'name',
'street_address',
'street_address2',
'city',
'state',
'zip_code',
]
list_filter = ['city', 'state']

@ -0,0 +1,6 @@
from django.apps import AppConfig
class JobsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "jobs"

@ -0,0 +1,549 @@
# Generated by Django 4.1.4 on 2022-12-29 18:09
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Address",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(default="Jobsite", max_length=100)),
("street_address", models.CharField(max_length=100)),
("street_address2", models.CharField(blank=True, max_length=100)),
("city", models.CharField(max_length=100)),
(
"state",
models.CharField(
choices=[
("AL", "Alabama"),
("AK", "Alaska"),
("AS", "American Samoa"),
("AZ", "Arizona"),
("AR", "Arkansas"),
("AA", "Armed Forces Americas"),
("AE", "Armed Forces Europe"),
("AP", "Armed Forces Pacific"),
("CA", "California"),
("CO", "Colorado"),
("CT", "Connecticut"),
("DE", "Delaware"),
("DC", "District of Columbia"),
("FL", "Florida"),
("GA", "Georgia"),
("GU", "Guam"),
("HI", "Hawaii"),
("ID", "Idaho"),
("IL", "Illinois"),
("IN", "Indiana"),
("IA", "Iowa"),
("KS", "Kansas"),
("KY", "Kentucky"),
("LA", "Louisiana"),
("ME", "Maine"),
("MD", "Maryland"),
("MA", "Massachusetts"),
("MI", "Michigan"),
("MN", "Minnesota"),
("MS", "Mississippi"),
("MO", "Missouri"),
("MT", "Montana"),
("NE", "Nebraska"),
("NV", "Nevada"),
("NH", "New Hampshire"),
("NJ", "New Jersey"),
("NM", "New Mexico"),
("NY", "New York"),
("NC", "North Carolina"),
("ND", "North Dakota"),
("MP", "Northern Mariana Islands"),
("OH", "Ohio"),
("OK", "Oklahoma"),
("OR", "Oregon"),
("PA", "Pennsylvania"),
("PR", "Puerto Rico"),
("RI", "Rhode Island"),
("SC", "South Carolina"),
("SD", "South Dakota"),
("TN", "Tennessee"),
("TX", "Texas"),
("UT", "Utah"),
("VT", "Vermont"),
("VI", "Virgin Islands"),
("VA", "Virginia"),
("WA", "Washington"),
("WV", "West Virginia"),
("WI", "Wisconsin"),
("WY", "Wyoming"),
],
max_length=2,
),
),
("zip_code", models.CharField(max_length=5)),
],
options={
"verbose_name_plural": "addresses",
"ordering": ["state", "zip_code", "street_address", "street_address2"],
"unique_together": {
("street_address", "street_address2", "city", "state", "zip_code")
},
},
),
migrations.CreateModel(
name="Company",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
],
options={
"verbose_name_plural": "companies",
"ordering": ["name"],
},
),
migrations.CreateModel(
name="Contact",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("email", models.EmailField(blank=True, max_length=254)),
(
"company",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="jobs.company"
),
),
],
options={
"ordering": ["name"],
"abstract": False,
},
),
migrations.CreateModel(
name="Detailer",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("email", models.EmailField(blank=True, max_length=254)),
],
options={
"ordering": ["name"],
"abstract": False,
},
),
migrations.CreateModel(
name="Job",
fields=[
("number", models.IntegerField(primary_key=True, serialize=False)),
("name", models.CharField(max_length=100)),
("award_date", models.DateField()),
("modeling", models.BooleanField(verbose_name="3D modeling included")),
(
"model_upload",
models.CharField(
blank=True, max_length=200, verbose_name="3D model upload link"
),
),
("addresses", models.ManyToManyField(blank=True, to="jobs.address")),
(
"architect",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="architect_jobs",
to="jobs.company",
),
),
("contacts", models.ManyToManyField(blank=True, to="jobs.contact")),
(
"contractor",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="contractor_jobs",
to="jobs.company",
),
),
(
"customer",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="customer_jobs",
to="jobs.company",
),
),
(
"engineers",
models.ManyToManyField(
blank=True, related_name="engineer_jobs", to="jobs.company"
),
),
(
"eor",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="eor_jobs",
to="jobs.company",
verbose_name="engineer of record",
),
),
(
"fab_detailers",
models.ManyToManyField(
blank=True, related_name="fab_jobs", to="jobs.detailer"
),
),
(
"installers",
models.ManyToManyField(
blank=True, related_name="installer_jobs", to="jobs.company"
),
),
],
options={
"ordering": ["number"],
},
),
migrations.CreateModel(
name="ProjectManager",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("email", models.EmailField(blank=True, max_length=254)),
],
options={
"ordering": ["name"],
"abstract": False,
},
),
migrations.CreateModel(
name="Stair",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("grid", models.CharField(max_length=100)),
("levels", models.CharField(max_length=100)),
("flights", models.IntegerField()),
(
"stringer_type",
models.CharField(
blank=True,
choices=[
("C", "C Channel"),
("MC", "MC Channel"),
("PL", "Plate"),
("CU", "Custom"),
],
default="PL",
max_length=2,
),
),
("stringer_custom", models.CharField(blank=True, max_length=50)),
(
"stair_con",
models.CharField(
blank=True,
choices=[
("PF", "Pan-filled"),
("CP", "Checker"),
("PC", "Precast"),
("GT", "Grating"),
("CU", "Custom"),
("BO", "By other"),
],
default="PF",
max_length=2,
verbose_name="stair construction",
),
),
(
"stair_con_custom",
models.CharField(
blank=True,
max_length=50,
verbose_name="custom stair construction",
),
),
("stair_qt", models.BooleanField(verbose_name="stair quiet tread")),
("landings", models.IntegerField()),
(
"land_con",
models.CharField(
blank=True,
choices=[
("PF", "Pan-filled"),
("CP", "Checker"),
("PC", "Precast"),
("GT", "Grating"),
("CU", "Custom"),
("BO", "By other"),
],
default="PF",
max_length=2,
verbose_name="landing construction",
),
),
(
"land_con_custom",
models.CharField(
blank=True,
max_length=50,
verbose_name="custom landing construction",
),
),
("land_qt", models.BooleanField(verbose_name="landing quiet tread")),
(
"rail_type",
models.CharField(
blank=True,
choices=[
("1", "100 Pipe"),
("2", "200 Pipe"),
("3", "300 Rec tube"),
("4", "400 Picket"),
("5", "500 Mesh"),
("6", "600 Cable"),
("7", "700 Rod"),
("8", "800 Glass"),
("C", "Custom"),
("B", "By other"),
],
default="2",
max_length=1,
),
),
("rail_type_custom", models.CharField(blank=True, max_length=50)),
(
"stn",
models.CharField(blank=True, max_length=50, verbose_name="STN"),
),
],
),
migrations.CreateModel(
name="Submittal",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("number", models.IntegerField()),
(
"job",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="jobs.job"
),
),
],
),
migrations.CreateModel(
name="StairRevision",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("number", models.PositiveIntegerField()),
(
"to_engr",
models.DateField(
blank=True, null=True, verbose_name="sent to engineer"
),
),
(
"due_from_engr",
models.DateField(
blank=True, null=True, verbose_name="due from engineer"
),
),
(
"rec_from_engr",
models.DateField(
blank=True, null=True, verbose_name="received from engineer"
),
),
("submitted", models.DateField(blank=True, null=True)),
(
"rec_from_cust",
models.DateField(
blank=True, null=True, verbose_name="received from customer"
),
),
(
"status",
models.CharField(
choices=[
("NS", "Not started"),
("IP", "In progress"),
("SU", "Submitted"),
("RT", "Returned"),
],
max_length=2,
),
),
(
"response",
models.CharField(
blank=True,
choices=[
("NC", "Approved"),
("AN", "Approved as noted"),
("RR", "Revise and resubmit"),
("U", "Update"),
],
max_length=2,
),
),
("stair_fabs", models.DateField(blank=True, null=True)),
("land_fabs", models.DateField(blank=True, null=True)),
("rail_fabs", models.DateField(blank=True, null=True)),
("embeds_shop", models.DateField(blank=True, null=True)),
("stairs_shop", models.DateField(blank=True, null=True)),
("lands_shop", models.DateField(blank=True, null=True)),
(
"stair",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="jobs.stair"
),
),
],
),
migrations.AddField(
model_name="stair",
name="submittal",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="jobs.submittal"
),
),
migrations.CreateModel(
name="PhoneNumber",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(default="Main", max_length=100)),
("number", models.CharField(max_length=25)),
(
"owner",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="jobs.contact"
),
),
],
),
migrations.CreateModel(
name="JobNote",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("date", models.DateTimeField(auto_now=True)),
("note", models.TextField(max_length=1000)),
("link", models.CharField(blank=True, max_length=300)),
(
"job",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="jobs.job"
),
),
],
),
migrations.AddField(
model_name="job",
name="project_manager",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="jobs.projectmanager",
),
),
migrations.AddField(
model_name="job",
name="sub_detailers",
field=models.ManyToManyField(
blank=True, related_name="sub_jobs", to="jobs.detailer"
),
),
]

@ -0,0 +1,316 @@
import datetime
from django.db import models
from localflavor.us.us_states import STATE_CHOICES
class Address(models.Model):
name = models.CharField(max_length=100, default='Jobsite')
street_address = models.CharField(max_length=100)
street_address2 = models.CharField(max_length=100, blank=True)
city = models.CharField(max_length=100)
state = models.CharField(max_length=2, choices=STATE_CHOICES)
zip_code = models.CharField(max_length=5)
def location(self):
return f'{self.city}, {self.state}'
def __str__(self):
string = f'{self.name}\n{self.street_address}\n'
if self.street_address2 != '':
string += f'{self.street_address}\n'
string += f'{self.location()} {self.zip_code}'
return string
class Meta:
verbose_name_plural = 'addresses'
unique_together = [[
'street_address',
'street_address2',
'city',
'state',
'zip_code',
]]
ordering = ['state', 'zip_code', 'street_address', 'street_address2']
class Company(models.Model):
'''A company we do business with'''
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'companies'
ordering = ['name']
class Person(models.Model):
'''Abstract class representing any person'''
name = models.CharField(max_length=100)
email = models.EmailField(blank=True)
def __str__(self):
return self.name
class Meta:
abstract = True
ordering = ['name']
class Contact(Person):
'''A contact at a client company'''
company = models.ForeignKey(Company, on_delete=models.PROTECT)
class PhoneNumber(models.Model):
'''A phone number for a Contact'''
owner = models.ForeignKey(Contact, on_delete=models.CASCADE)
name = models.CharField(max_length=100, default='Main')
number = models.CharField(max_length=25)
def __str__(self):
return f'{self.name}: {self.number}'
class Detailer(Person):
'''A PSC detailer'''
class ProjectManager(Person):
'''A PSC project manager'''
class Job(models.Model):
number = models.IntegerField(primary_key=True)
name = models.CharField(max_length=100)
addresses = models.ManyToManyField(
Address,
blank=True,
)
contractor = models.ForeignKey(
Company,
on_delete=models.PROTECT,
related_name='contractor_jobs',
blank=True,
null=True,
)
architect = models.ForeignKey(
Company,
on_delete=models.PROTECT,
related_name='architect_jobs',
blank=True,
null=True,
)
eor = models.ForeignKey(
Company,
on_delete=models.PROTECT,
related_name='eor_jobs',
verbose_name='engineer of record',
blank=True,
null=True,
)
customer = models.ForeignKey(
Company,
on_delete=models.PROTECT,
related_name='customer_jobs',
blank=True,
null=True,
)
contacts = models.ManyToManyField(Contact, blank=True)
project_manager = models.ForeignKey(
ProjectManager,
on_delete=models.PROTECT,
blank=True,
null=True,
)
sub_detailers = models.ManyToManyField(
Detailer,
related_name='sub_jobs',
blank=True,
)
fab_detailers = models.ManyToManyField(
Detailer,
related_name='fab_jobs',
blank=True,
)
engineers = models.ManyToManyField(
Company,
related_name='engineer_jobs',
blank=True,
)
installers = models.ManyToManyField(
Company,
related_name='installer_jobs',
blank=True,
)
award_date = models.DateField()
def award_plus_8(self):
return self.award_date + datetime.timedelta(weeks=8)
def award_plus_10(self):
return self.award_date + datetime.timedelta(weeks=10)
modeling = models.BooleanField('3D modeling included')
model_upload = models.CharField(
'3D model upload link',
max_length=200,
blank=True,
)
def __str__(self):
return f"{self.number} - {self.name}"
class Meta:
ordering = ['number']
class JobNote(models.Model):
job = models.ForeignKey(Job, on_delete=models.CASCADE)
date = models.DateTimeField(auto_now=True)
note = models.TextField(max_length=1000)
link = models.CharField(max_length=300, blank=True)
class Submittal(models.Model):
job = models.ForeignKey(Job, on_delete=models.CASCADE)
number = models.IntegerField()
def __str__(self):
return f"{self.job} sub {self.number}"
class Stair(models.Model):
submittal = models.ForeignKey(Submittal, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
grid = models.CharField(max_length=100)
levels = models.CharField(max_length=100)
flights = models.IntegerField()
STRINGER_CHOICES = [
('C', 'C Channel'),
('MC', 'MC Channel'),
('PL', 'Plate'),
('CU', 'Custom'),
]
stringer_type = models.CharField(
max_length=2,
choices=STRINGER_CHOICES,
default='PL',
blank=True,
)
stringer_custom = models.CharField(max_length=50, blank=True)
CONSTRUCTION_CHOICES = [
('PF', 'Pan-filled'),
('CP', 'Checker'),
('PC', 'Precast'),
('GT', 'Grating'),
('CU', 'Custom'),
('BO', 'By other'),
]
stair_con = models.CharField(
'stair construction',
max_length=2,
choices=CONSTRUCTION_CHOICES,
default='PF',
blank=True,
)
stair_con_custom = models.CharField(
'custom stair construction',
max_length=50,
blank=True
)
stair_qt = models.BooleanField('stair quiet tread')
landings = models.IntegerField()
land_con = models.CharField(
'landing construction',
max_length=2,
choices=CONSTRUCTION_CHOICES,
default='PF',
blank=True,
)
land_con_custom = models.CharField(
'custom landing construction',
max_length=50,
blank=True
)
land_qt = models.BooleanField('landing quiet tread')
RAIL_CHOICES = [
('1', '100 Pipe'),
('2', '200 Pipe'),
('3', '300 Rec tube'),
('4', '400 Picket'),
('5', '500 Mesh'),
('6', '600 Cable'),
('7', '700 Rod'),
('8', '800 Glass'),
('C', 'Custom'),
('B', 'By other'),
]
rail_type = models.CharField(
max_length=1,
choices=RAIL_CHOICES,
default='2',
blank=True,
)
rail_type_custom = models.CharField(max_length=50, blank=True)
stn = models.CharField('STN', max_length=50, blank=True)
def __str__(self):
return f"{self.submittal} stair {self.name}"
class StairRevision(models.Model):
stair = models.ForeignKey(Stair, on_delete=models.PROTECT)
number = models.PositiveIntegerField()
to_engr = models.DateField(
'sent to engineer',
blank=True,
null=True,
)
due_from_engr = models.DateField(
'due from engineer',
blank=True,
null=True,
)
rec_from_engr = models.DateField(
'received from engineer',
blank=True,
null=True,
)
submitted = models.DateField(blank=True, null=True)
rec_from_cust = models.DateField(
'received from customer',
blank=True,
null=True,
)
STATUS_CHOICES = [
('NS', 'Not started'),
('IP', 'In progress'),
('SU', 'Submitted'),
('RT', 'Returned'),
]
status = models.CharField(
max_length=2,
choices=STATUS_CHOICES,
)
RESPONSE_CHOICES = [
('NC', 'Approved'),
('AN', 'Approved as noted'),
('RR', 'Revise and resubmit'),
('U', 'Update'),
]
response = models.CharField(
max_length=2,
choices=RESPONSE_CHOICES,
blank=True,
)
stair_fabs = models.DateField(blank=True, null=True)
land_fabs = models.DateField(blank=True, null=True)
rail_fabs = models.DateField(blank=True, null=True)
embeds_shop = models.DateField(blank=True, null=True)
stairs_shop = models.DateField(blank=True, null=True)
lands_shop = models.DateField(blank=True, null=True)
def __str__(self):
return f"{self.stair} rev {self.number}"

@ -0,0 +1,4 @@
body {
background-color: #dddddd;
padding: 0 20px 20px 20px;
}

@ -0,0 +1,16 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="{% static 'jobs/style.css' %}"/>
<title>{% block title %}Drawing Status{% endblock %}</title>
</head>
<body>
<h1>Drawing Status</h1>
<ul id="menu">
<li><a href="/">Job List</a></li>
</ul>
{% block content %}{% endblock %}
</body>
</html>

@ -0,0 +1,66 @@
{% extends "base.html" %}
{% block title %}{{block.super}} | {{ job }}{% endblock %}
{% block content %}
<h2>{{ job }}</h2>
<dl id="cust_info">
<dt>Addresses</dt>
{% for address in job.addresses.all %}
<dd>{{ address|linebreaks }}</dd>
{% endfor %}
<dt>Contractor</dt>
<dd>{{ job.contractor }}</dd>
<dt>Architect</dt>
<dd>{{ job.architect }}</dd>
<dt>Engineer of Record</dt>
<dd>{{ job.eor }}</dd>
<dt>Customer</dt>
<dd>{{ job.customer }}</dd>
<dt>Contact{{ job.contacts.all|pluralize }}</dt>
{% for contact in job.contacts.all %}
<dd><a href="mailto:{{contact.email}}">{{ contact.name }}</a></dd>
{% endfor %}
</dl>
<dl id="award_date">
<dt>Award Date</dt>
<dd>{{ job.award_date }}</dd>
<dt>+8 week lead</dt>
<dd>{{ job.award_plus_8 }}</dd>
<dt>+10 week lead</dt>
<dd>{{ job.award_plus_10 }}</dd>
<dt>Current Delta</dt>
<dd>{{ job.award_date|timesince }}</dd>
</dl>
<dl id="inhouse_info">
<dt>Project Manager</dt>
<dd>{{ job.project_manager }}</dd>
<dt>Submittal Detailer{{ job.sub_detailers.all|pluralize }}</dt>
{% for detailer in job.sub_detailers.all %}
<dd>{{ detailer }}</dd>
{% endfor %}
<dt>Fab Detailer{{ job.fab_detailers.all|pluralize }}</dt>
{% for detailer in job.fab_detailers.all %}
<dd>{{ detailer }}</dd>
{% endfor %}
<dt>Engineer{{ job.engineers.all|pluralize }}</dt>
{% for egr in job.engineers.all %}
<dd>{{ egr }}</dd>
{% endfor %}
</dl>
{% endblock %}

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}{{ block.super }} | Index{% endblock %}
{% block content %}
<h2>Index</h2>
<ul>
{% for job in jobs %}
<li><a href="{% url 'jobs:detail' job.number %}">{{ job }}</a></li>
{% endfor %}
</ul>
{% endblock %}

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

@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = 'jobs'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
]

@ -0,0 +1,17 @@
from django.views import generic
from .models import Job
class IndexView(generic.ListView):
template_name = 'jobs/index.html'
context_object_name = 'jobs'
def get_queryset(self):
return Job.objects.all()
class DetailView(generic.DetailView):
model = Job
template_name = 'jobs/detail.html'

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dwgstatus.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
Loading…
Cancel
Save