From 090bb9e07a02f65ed34a597ecee70c6a90e4307d Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Tue, 23 Mar 2021 17:08:00 -0400 Subject: [PATCH 01/24] Initial Commit, basic models/admin pages for CMS_Database tables --- .gitignore | 2 + Pipfile | 13 ++++ Pipfile.lock | 63 ++++++++++++++++ manage.py | 22 ++++++ member_paperwork/__init__.py | 0 member_paperwork/asgi.py | 16 ++++ member_paperwork/settings/.gitignore | 2 + member_paperwork/settings/__init__.py | 0 member_paperwork/settings/base.py | 92 ++++++++++++++++++++++ member_paperwork/settings/dev_base.py | 7 ++ member_paperwork/urls.py | 21 ++++++ member_paperwork/wsgi.py | 16 ++++ paperwork/__init__.py | 0 paperwork/admin.py | 32 ++++++++ paperwork/apps.py | 5 ++ paperwork/models.py | 105 ++++++++++++++++++++++++++ paperwork/routers.py | 21 ++++++ paperwork/tests.py | 3 + paperwork/views.py | 3 + 19 files changed, 423 insertions(+) create mode 100644 .gitignore create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100755 manage.py create mode 100644 member_paperwork/__init__.py create mode 100644 member_paperwork/asgi.py create mode 100644 member_paperwork/settings/.gitignore create mode 100644 member_paperwork/settings/__init__.py create mode 100644 member_paperwork/settings/base.py create mode 100644 member_paperwork/settings/dev_base.py create mode 100644 member_paperwork/urls.py create mode 100644 member_paperwork/wsgi.py create mode 100644 paperwork/__init__.py create mode 100644 paperwork/admin.py create mode 100644 paperwork/apps.py create mode 100644 paperwork/models.py create mode 100644 paperwork/routers.py create mode 100644 paperwork/tests.py create mode 100644 paperwork/views.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ea61b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.sqlite3 diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..140f778 --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +django = "*" +mysqlclient = "*" + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..8b6f552 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,63 @@ +{ + "_meta": { + "hash": { + "sha256": "d1bf637f40b6f55b53eed51857df5d23fab51ea260b3c5b04710b8b94b80f803" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "asgiref": { + "hashes": [ + "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", + "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" + ], + "markers": "python_version >= '3.5'", + "version": "==3.3.1" + }, + "django": { + "hashes": [ + "sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7", + "sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8" + ], + "index": "pypi", + "version": "==3.1.7" + }, + "mysqlclient": { + "hashes": [ + "sha256:0ac0dd759c4ca02c35a9fedc24bc982cf75171651e8187c2495ec957a87dfff7", + "sha256:3381ca1a4f37ff1155fcfde20836b46416d66531add8843f6aa6d968982731c3", + "sha256:71c4b330cf2313bbda0307fc858cc9055e64493ba9bf28454d25cf8b3ee8d7f5", + "sha256:f6ebea7c008f155baeefe16c56cd3ee6239f7a5a9ae42396c2f1860f08a7c432", + "sha256:fc575093cf81b6605bed84653e48b277318b880dc9becf42dd47fa11ffd3e2b6" + ], + "index": "pypi", + "version": "==2.0.3" + }, + "pytz": { + "hashes": [ + "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", + "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" + ], + "version": "==2021.1" + }, + "sqlparse": { + "hashes": [ + "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", + "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" + ], + "markers": "python_version >= '3.5'", + "version": "==0.4.1" + } + }, + "develop": {} +} diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..2e04034 --- /dev/null +++ b/manage.py @@ -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', 'member_paperwork.settings.dev') + 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() diff --git a/member_paperwork/__init__.py b/member_paperwork/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/member_paperwork/asgi.py b/member_paperwork/asgi.py new file mode 100644 index 0000000..c40b727 --- /dev/null +++ b/member_paperwork/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for member_paperwork 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/3.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'member_paperwork.settings') + +application = get_asgi_application() diff --git a/member_paperwork/settings/.gitignore b/member_paperwork/settings/.gitignore new file mode 100644 index 0000000..a2ac02f --- /dev/null +++ b/member_paperwork/settings/.gitignore @@ -0,0 +1,2 @@ +dev.py +prod.py diff --git a/member_paperwork/settings/__init__.py b/member_paperwork/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/member_paperwork/settings/base.py b/member_paperwork/settings/base.py new file mode 100644 index 0000000..af22ebc --- /dev/null +++ b/member_paperwork/settings/base.py @@ -0,0 +1,92 @@ +""" +Django settings for member_paperwork project. + +Generated by 'django-admin startproject' using Django 3.1.7. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent.parent + + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'paperwork.apps.PaperworkConfig', +] + +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 = 'member_paperwork.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 = 'member_paperwork.wsgi.application' + + +DATABASE_ROUTERS = ['paperwork.routers.PaperworkRouter'] + + +# Password validation +# https://docs.djangoproject.com/en/3.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/3.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_L10N = True +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.1/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/member_paperwork/settings/dev_base.py b/member_paperwork/settings/dev_base.py new file mode 100644 index 0000000..e8aab68 --- /dev/null +++ b/member_paperwork/settings/dev_base.py @@ -0,0 +1,7 @@ +from .base import * + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True diff --git a/member_paperwork/urls.py b/member_paperwork/urls.py new file mode 100644 index 0000000..f1b9910 --- /dev/null +++ b/member_paperwork/urls.py @@ -0,0 +1,21 @@ +"""member_paperwork URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.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 + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/member_paperwork/wsgi.py b/member_paperwork/wsgi.py new file mode 100644 index 0000000..1f29a41 --- /dev/null +++ b/member_paperwork/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for member_paperwork 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/3.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'member_paperwork.settings') + +application = get_wsgi_application() diff --git a/paperwork/__init__.py b/paperwork/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/paperwork/admin.py b/paperwork/admin.py new file mode 100644 index 0000000..2658089 --- /dev/null +++ b/paperwork/admin.py @@ -0,0 +1,32 @@ +from django.contrib import admin + +from .models import (CmsRedRiverVeteransScholarship, CertificationDefinition, + Certification, InstructorOrVendor, SpecialProgram, Waiver) + + +class CertificationDefinitionAdmin(admin.ModelAdmin): + search_fields = ['certification_name', 'department'] + + +class CertificationAdmin(admin.ModelAdmin): + search_fields = ['name', 'certification__certification_name', 'certification__department'] + + +class InstructorOrVendorAdmin(admin.ModelAdmin): + search_fields = ['name'] + + +class SpecialProgramAdmin(admin.ModelAdmin): + search_fields = ['program_name'] + + +class WaiverAdmin(admin.ModelAdmin): + search_fields = ['name'] + + +admin.site.register(CmsRedRiverVeteransScholarship) +admin.site.register(CertificationDefinition, CertificationDefinitionAdmin) +admin.site.register(Certification, CertificationAdmin) +admin.site.register(InstructorOrVendor, InstructorOrVendorAdmin) +admin.site.register(SpecialProgram, SpecialProgramAdmin) +admin.site.register(Waiver, WaiverAdmin) diff --git a/paperwork/apps.py b/paperwork/apps.py new file mode 100644 index 0000000..2bdd60a --- /dev/null +++ b/paperwork/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class PaperworkConfig(AppConfig): + name = 'paperwork' diff --git a/paperwork/models.py b/paperwork/models.py new file mode 100644 index 0000000..35ec46d --- /dev/null +++ b/paperwork/models.py @@ -0,0 +1,105 @@ +from django.db import models + + +class CmsRedRiverVeteransScholarship(models.Model): + serial = models.AutoField(primary_key=True) + program_name = models.CharField(db_column='Program Name', max_length=255) + member_name = models.CharField(db_column='Member Name', max_length=255, blank=True, null=True) + discount_percent = models.DecimalField(db_column='Discount Percent', max_digits=16, decimal_places=0, blank=True, null=True) + discount_code = models.CharField(db_column='Discount Code', max_length=255, blank=True, null=True) + membership_code = models.CharField(db_column='Membership Code', max_length=255, blank=True, null=True) + start_date = models.DateField(db_column='Start Date', blank=True, null=True) + end_date = models.DateField(db_column='End Date', blank=True, null=True) + program_amount = models.DecimalField(db_column='Program Amount', max_digits=16, decimal_places=0, blank=True, null=True) + program_status = models.CharField(db_column='Program Status', max_length=16, blank=True, null=True) + + def __str__(self): + return f"{self.program_name} {self.member_name}" + + class Meta: + managed = False + db_table = 'CMS Red River Veterans Scholarship' + + +class CertificationDefinition(models.Model): + certification_identifier = models.AutoField(db_column='Certification Identifier', primary_key=True) + certification_name = models.CharField(db_column='Certification Name', max_length=255, blank=True, null=True) + department = models.CharField(db_column='Department', max_length=255, blank=True, null=True) + + def __str__(self): + return f"{self.department}: {self.certification_name}" + + class Meta: + managed = False + db_table = 'Certification Definitions' + + +class Certification(models.Model): + number = models.AutoField(db_column='Number', primary_key=True) + certification = models.ForeignKey(CertificationDefinition, on_delete=models.PROTECT, db_column='Certification') + name = models.CharField(db_column='Name', max_length=255) + uid = models.CharField(max_length=24, blank=True, null=True) + certified_by = models.CharField(db_column='Certified_By', max_length=255, blank=True, null=True) + date = models.DateField(db_column='Date', blank=True, null=True) + version = models.CharField(db_column='Version', max_length=255, blank=True, null=True) + shop_lead_notified = models.DateTimeField(db_column='Shop Lead Notified', blank=True, null=True) + + def __str__(self): + return f"{self.name} - {self.certification} [{self.version}]" + + class Meta: + managed = False + db_table = 'Certifications' + + +class InstructorOrVendor(models.Model): + serial = models.AutoField(primary_key=True) + name = models.CharField(db_column='Name', max_length=255) + instructor_agreement_date = models.DateField(db_column='Instructor Agreement Date', blank=True, null=True) + w9_date = models.DateField(db_column='W9 date', blank=True, null=True) + phone = models.CharField(max_length=255, blank=True, null=True) + email_address = models.CharField(db_column='email address', max_length=255, blank=True, null=True) + + def __str__(self): + return f"{self.name}" + + class Meta: + managed = False + db_table = 'Instructors and Vendors' + + +class SpecialProgram(models.Model): + program_name = models.CharField(db_column='Program Name', primary_key=True, max_length=255) + discount_percent = models.DecimalField(db_column='Discount Percent', max_digits=16, decimal_places=0, blank=True, null=True) + discount_code = models.CharField(db_column='Discount Code', max_length=255, blank=True, null=True) + membership_code = models.CharField(db_column='Membership Code', max_length=255, blank=True, null=True) + start_date = models.DateField(db_column='Start Date', blank=True, null=True) + end_date = models.DateField(db_column='End Date', blank=True, null=True) + program_amount = models.DecimalField(db_column='Program Amount', max_digits=16, decimal_places=0, blank=True, null=True) + program_status = models.CharField(db_column='Program Status', max_length=16, blank=True, null=True) + + def __str__(self): + return self.program_name + + class Meta: + managed = False + db_table = 'Special_Programs' + + +class Waiver(models.Model): + number = models.AutoField(db_column='Number', primary_key=True) + name = models.CharField(db_column='Name', max_length=255) + date = models.DateField(db_column='Date') + emergency_contact_name = models.CharField(db_column='Emergency Contact Name', max_length=255, blank=True, null=True) + emergency_contact_number = models.CharField(db_column='Emergency Contact Number', max_length=25, blank=True, null=True) + waiver_version = models.CharField(db_column='Waiver version', max_length=64) + guardian_name = models.CharField(db_column='Guardian Name', max_length=255, blank=True, null=True) + guardian_relation = models.CharField(db_column='Guardian Relation', max_length=255, blank=True, null=True) + guardian_date = models.DateField(db_column='Guardian Date', blank=True, null=True) + + def __str__(self): + return f"{self.name} {self.date}" + + class Meta: + managed = False + db_table = 'Waivers' diff --git a/paperwork/routers.py b/paperwork/routers.py new file mode 100644 index 0000000..9c13406 --- /dev/null +++ b/paperwork/routers.py @@ -0,0 +1,21 @@ +class PaperworkRouter: + app_label = 'paperwork' + db = 'cms' + + def db_for_read(self, model, **hints): + if model._meta.app_label == self.app_label: + return self.db + return None + + def db_for_write(self, model, **hints): + if model._meta.app_label == self.app_label: + return self.db + return None + + def allow_relation(self, obj1, obj2, **hints): + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if db == self.db: + return False + return None diff --git a/paperwork/tests.py b/paperwork/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/paperwork/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/paperwork/views.py b/paperwork/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/paperwork/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 16eb034b03e14ae77d9d3431dd55e69bccf851b5 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Tue, 23 Mar 2021 17:11:11 -0400 Subject: [PATCH 02/24] Add Member table from membershipworks database --- member_paperwork/settings/base.py | 2 +- paperwork/admin.py | 17 +++++++++++++++-- paperwork/models.py | 19 ++++++++++++++++++- paperwork/routers.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/member_paperwork/settings/base.py b/member_paperwork/settings/base.py index af22ebc..33a4ef5 100644 --- a/member_paperwork/settings/base.py +++ b/member_paperwork/settings/base.py @@ -62,7 +62,7 @@ TEMPLATES = [ WSGI_APPLICATION = 'member_paperwork.wsgi.application' -DATABASE_ROUTERS = ['paperwork.routers.PaperworkRouter'] +DATABASE_ROUTERS = ['paperwork.routers.MembershipWorksRouter', 'paperwork.routers.PaperworkRouter'] # Password validation diff --git a/paperwork/admin.py b/paperwork/admin.py index 2658089..d43ec70 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -1,7 +1,9 @@ from django.contrib import admin -from .models import (CmsRedRiverVeteransScholarship, CertificationDefinition, - Certification, InstructorOrVendor, SpecialProgram, Waiver) +from .models import (CmsRedRiverVeteransScholarship, + CertificationDefinition, Certification, + InstructorOrVendor, SpecialProgram, Waiver, + Member) class CertificationDefinitionAdmin(admin.ModelAdmin): @@ -24,9 +26,20 @@ class WaiverAdmin(admin.ModelAdmin): search_fields = ['name'] +class MemberAdmin(admin.ModelAdmin): + readonly_fields = [field.name for field in Member._meta.get_fields() if field.concrete] + + def has_add_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + admin.site.register(CmsRedRiverVeteransScholarship) admin.site.register(CertificationDefinition, CertificationDefinitionAdmin) admin.site.register(Certification, CertificationAdmin) admin.site.register(InstructorOrVendor, InstructorOrVendorAdmin) admin.site.register(SpecialProgram, SpecialProgramAdmin) admin.site.register(Waiver, WaiverAdmin) +admin.site.register(Member, MemberAdmin) diff --git a/paperwork/models.py b/paperwork/models.py index 35ec46d..9b421ce 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -1,6 +1,22 @@ from django.db import models +class Member(models.Model): + # TODO: this is only a partial set of fields, may want to expand + uid = models.CharField(primary_key=True, max_length=24) + year_of_birth = models.TextField(db_column='Year of Birth', blank=True, null=True) + account_name = models.TextField(db_column='Account Name', blank=True, null=True) + first_name = models.TextField(db_column='First Name', blank=True, null=True) + last_name = models.TextField(db_column='Last Name', blank=True, null=True) + + def __str__(self): + return f"{self.account_name}" + + class Meta: + managed = False + db_table = 'members' + + class CmsRedRiverVeteransScholarship(models.Model): serial = models.AutoField(primary_key=True) program_name = models.CharField(db_column='Program Name', max_length=255) @@ -38,7 +54,8 @@ class Certification(models.Model): number = models.AutoField(db_column='Number', primary_key=True) certification = models.ForeignKey(CertificationDefinition, on_delete=models.PROTECT, db_column='Certification') name = models.CharField(db_column='Name', max_length=255) - uid = models.CharField(max_length=24, blank=True, null=True) + # TODO: this should be a fk to the membershipworks.members table + member = models.ForeignKey(Member, on_delete=models.PROTECT, to_field='uid', db_column='uid', blank=True, null=True) certified_by = models.CharField(db_column='Certified_By', max_length=255, blank=True, null=True) date = models.DateField(db_column='Date', blank=True, null=True) version = models.CharField(db_column='Version', max_length=255, blank=True, null=True) diff --git a/paperwork/routers.py b/paperwork/routers.py index 9c13406..e532f1f 100644 --- a/paperwork/routers.py +++ b/paperwork/routers.py @@ -1,3 +1,6 @@ +from .models import Member + + class PaperworkRouter: app_label = 'paperwork' db = 'cms' @@ -12,6 +15,32 @@ class PaperworkRouter: return self.db return None + def allow_relation(self, obj1, obj2, **hints): + if (obj1 == Member and obj2._meta.app_label == self.app_label) or \ + (obj2 == Member and obj1._meta.app_label == self.app_label): + return True + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if db == self.db: + return False + return None + + +# TODO: this is pretty hacky/special cased +class MembershipWorksRouter: + db = 'membershipworks' + + def db_for_read(self, model, **hints): + if model == Member: + return self.db + return None + + def db_for_write(self, model, **hints): + if model == Member: + return self.db + return None + def allow_relation(self, obj1, obj2, **hints): return None From a5cd992c0a1de293bc395de68209cb05af2b64cd Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Wed, 24 Mar 2021 14:33:07 -0400 Subject: [PATCH 03/24] Add Authentication via LDAP --- Pipfile | 1 + Pipfile.lock | 53 +++++++++++++++++++++++++- member_paperwork/settings/base.py | 11 ------ member_paperwork/settings/prod_base.py | 41 ++++++++++++++++++++ 4 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 member_paperwork/settings/prod_base.py diff --git a/Pipfile b/Pipfile index 140f778..77c5f99 100644 --- a/Pipfile +++ b/Pipfile @@ -6,6 +6,7 @@ name = "pypi" [packages] django = "*" mysqlclient = "*" +django-auth-ldap = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 8b6f552..d8e5fe6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d1bf637f40b6f55b53eed51857df5d23fab51ea260b3c5b04710b8b94b80f803" + "sha256": "d1de6cc0a8d6f8b2724fbc9180a4d439f784856895370901ecb45727dfcb8e02" }, "pipfile-spec": 6, "requires": { @@ -32,6 +32,14 @@ "index": "pypi", "version": "==3.1.7" }, + "django-auth-ldap": { + "hashes": [ + "sha256:5894317122a086c9955ed366562869a81459cf6b663636b152857bb5d3a0a3b7", + "sha256:cbbb476eff2504b5ab4fdf1fa92d93d2d3408fd9c8bc0c426169d987d0733153" + ], + "index": "pypi", + "version": "==2.3.0" + }, "mysqlclient": { "hashes": [ "sha256:0ac0dd759c4ca02c35a9fedc24bc982cf75171651e8187c2495ec957a87dfff7", @@ -43,6 +51,49 @@ "index": "pypi", "version": "==2.0.3" }, + "pyasn1": { + "hashes": [ + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + ], + "version": "==0.4.8" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", + "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", + "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", + "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", + "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", + "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", + "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", + "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", + "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", + "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", + "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", + "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", + "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" + ], + "version": "==0.2.8" + }, + "python-ldap": { + "hashes": [ + "sha256:4711cacf013e298754abd70058ccc995758177fb425f1c2d30e71adfc1d00aa5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==3.3.1" + }, "pytz": { "hashes": [ "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", diff --git a/member_paperwork/settings/base.py b/member_paperwork/settings/base.py index 33a4ef5..8fffde2 100644 --- a/member_paperwork/settings/base.py +++ b/member_paperwork/settings/base.py @@ -65,17 +65,6 @@ WSGI_APPLICATION = 'member_paperwork.wsgi.application' DATABASE_ROUTERS = ['paperwork.routers.MembershipWorksRouter', 'paperwork.routers.PaperworkRouter'] -# Password validation -# https://docs.djangoproject.com/en/3.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/3.1/topics/i18n/ diff --git a/member_paperwork/settings/prod_base.py b/member_paperwork/settings/prod_base.py new file mode 100644 index 0000000..2df0dde --- /dev/null +++ b/member_paperwork/settings/prod_base.py @@ -0,0 +1,41 @@ +import ldap +from django_auth_ldap.config import LDAPSearch, PosixGroupType, LDAPGroupQuery + +from .base import * + +DEBUG = False + + +# LDAP Authentication +# https://django-auth-ldap.readthedocs.io/en/latest/ +# "AUTH_LDAP_SERVER_URI", "AUTH_LDAP_BIND_DN", and "AUTH_LDAP_BIND_PASSWORD" set in prod.py + +AUTHENTICATION_BACKENDS = [ + 'django_auth_ldap.backend.LDAPBackend', + 'django.contrib.auth.backends.ModelBackend', +] + +AUTH_LDAP_USER_SEARCH = LDAPSearch( + 'cn=users,dc=sawtooth,dc=claremontmakerspace,dc=org', + ldap.SCOPE_SUBTREE, + '(uid=%(user)s)', +) + +AUTH_LDAP_USER_ATTR_MAP = { + 'first_name': 'givenName', + 'last_name': 'sn', + 'email': 'mail', +} + +AUTH_LDAP_USER_FLAGS_BY_GROUP = { + "is_staff": LDAPGroupQuery( + "cn=MW_CMS Staff,cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org"), +} + +AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + 'cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org', + ldap.SCOPE_SUBTREE, + '(objectClass=posixGroup)', +) +AUTH_LDAP_GROUP_TYPE = PosixGroupType() +AUTH_LDAP_MIRROR_GROUPS = True From 2690e2550d4b697691b57a246a67ba963cbbb664 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Wed, 24 Mar 2021 15:14:04 -0400 Subject: [PATCH 04/24] Add wsgi config file --- deploy/member_paperwork.ini | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 deploy/member_paperwork.ini diff --git a/deploy/member_paperwork.ini b/deploy/member_paperwork.ini new file mode 100644 index 0000000..51b8a7a --- /dev/null +++ b/deploy/member_paperwork.ini @@ -0,0 +1,14 @@ +[uwsgi] +plugins = python +socket = /tmp/uwsgi-member_paperwork.sock +master=True +pidfile=/tmp/member_paperwork-master.pid +vacuum=True +max-requests=5000 +uid = http +gid = http + +chdir=/usr/share/webapps/member_paperwork/ +module=member_paperwork.wsgi:application +env=DJANGO_SETTINGS_MODULE=member_paperwork.settings.prod +home=/usr/share/webapps/member_paperwork/.venv From 84ac1e8257f0474aa8799c29327a9cc62763b25e Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 29 Apr 2021 13:32:49 -0400 Subject: [PATCH 05/24] Fix `==` -> `isinstance` typo --- paperwork/routers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paperwork/routers.py b/paperwork/routers.py index e532f1f..4e2249c 100644 --- a/paperwork/routers.py +++ b/paperwork/routers.py @@ -16,8 +16,8 @@ class PaperworkRouter: return None def allow_relation(self, obj1, obj2, **hints): - if (obj1 == Member and obj2._meta.app_label == self.app_label) or \ - (obj2 == Member and obj1._meta.app_label == self.app_label): + if (isinstance(obj1, Member) and obj2._meta.app_label == self.app_label) or \ + (isinstance(obj2, Member) and obj1._meta.app_label == self.app_label): return True return None From 819c32b915dfb9f6a08bfc6858d1c9d914d39f2c Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 29 Apr 2021 13:50:49 -0400 Subject: [PATCH 06/24] Order members by first name, then last name --- paperwork/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/paperwork/models.py b/paperwork/models.py index 9b421ce..3b3915d 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -15,6 +15,7 @@ class Member(models.Model): class Meta: managed = False db_table = 'members' + ordering = ('first_name', 'last_name') class CmsRedRiverVeteransScholarship(models.Model): From c817ea85326202a81e1215c790427ed5b9777c78 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 29 Apr 2021 14:01:07 -0400 Subject: [PATCH 07/24] Change certification definition naming scheme and define ordering --- paperwork/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paperwork/models.py b/paperwork/models.py index 3b3915d..03ac08c 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -44,11 +44,12 @@ class CertificationDefinition(models.Model): department = models.CharField(db_column='Department', max_length=255, blank=True, null=True) def __str__(self): - return f"{self.department}: {self.certification_name}" + return f"{self.certification_name} <{self.department}>" class Meta: managed = False db_table = 'Certification Definitions' + ordering = ('certification_name', 'department') class Certification(models.Model): From 6a07e0be3ee4139d527876bea198c795be303367 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 3 Feb 2022 00:31:41 -0500 Subject: [PATCH 08/24] Specify minor versions of dependencies --- Pipfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Pipfile b/Pipfile index 77c5f99..a17a4b8 100644 --- a/Pipfile +++ b/Pipfile @@ -4,9 +4,9 @@ verify_ssl = true name = "pypi" [packages] -django = "*" -mysqlclient = "*" -django-auth-ldap = "*" +django = "~=3.2" +mysqlclient = "~=2.0" +django-auth-ldap = "~=2.4" [dev-packages] From 192145e32a78f47c0cd7fb6214b5e9d8489cc80f Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 3 Feb 2022 00:32:49 -0500 Subject: [PATCH 09/24] Update dependencies --- Pipfile | 6 +++--- Pipfile.lock | 53 +++++++++++++++++++++++----------------------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/Pipfile b/Pipfile index a17a4b8..e31c80c 100644 --- a/Pipfile +++ b/Pipfile @@ -4,9 +4,9 @@ verify_ssl = true name = "pypi" [packages] -django = "~=3.2" -mysqlclient = "~=2.0" -django-auth-ldap = "~=2.4" +django = "~=4.0" +mysqlclient = "~=2.1" +django-auth-ldap = "~=4.0" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index d8e5fe6..90fe008 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d1de6cc0a8d6f8b2724fbc9180a4d439f784856895370901ecb45727dfcb8e02" + "sha256": "64d22e44b33d1417c5433e61f41e79967fa16dfd4f2faa5787724bc0760f5f8c" }, "pipfile-spec": 6, "requires": { @@ -18,38 +18,38 @@ "default": { "asgiref": { "hashes": [ - "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", - "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" + "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", + "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" ], - "markers": "python_version >= '3.5'", - "version": "==3.3.1" + "markers": "python_version >= '3.7'", + "version": "==3.5.0" }, "django": { "hashes": [ - "sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7", - "sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8" + "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a", + "sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a" ], "index": "pypi", - "version": "==3.1.7" + "version": "==4.0.2" }, "django-auth-ldap": { "hashes": [ - "sha256:5894317122a086c9955ed366562869a81459cf6b663636b152857bb5d3a0a3b7", - "sha256:cbbb476eff2504b5ab4fdf1fa92d93d2d3408fd9c8bc0c426169d987d0733153" + "sha256:276f79e624ce083ce13f161387f65ff1c0efe83ef8a42f2b9830d43317b15239", + "sha256:94119c94981809124d3dc4bed974f71c7a980666896df626f556a88a5fe0b59c" ], "index": "pypi", - "version": "==2.3.0" + "version": "==4.0.0" }, "mysqlclient": { "hashes": [ - "sha256:0ac0dd759c4ca02c35a9fedc24bc982cf75171651e8187c2495ec957a87dfff7", - "sha256:3381ca1a4f37ff1155fcfde20836b46416d66531add8843f6aa6d968982731c3", - "sha256:71c4b330cf2313bbda0307fc858cc9055e64493ba9bf28454d25cf8b3ee8d7f5", - "sha256:f6ebea7c008f155baeefe16c56cd3ee6239f7a5a9ae42396c2f1860f08a7c432", - "sha256:fc575093cf81b6605bed84653e48b277318b880dc9becf42dd47fa11ffd3e2b6" + "sha256:02c8826e6add9b20f4cb12dcf016485f7b1d6e30356a1204d05431867a1b3947", + "sha256:2c8410f54492a3d2488a6a53e2d85b7e016751a1e7d116e7aea9c763f59f5e8c", + "sha256:973235686f1b720536d417bf0a0d39b4ab3d5086b2b6ad5e6752393428c02b12", + "sha256:b62d23c11c516cedb887377c8807628c1c65d57593b57853186a6ee18b0c6a5b", + "sha256:e6279263d5a9feca3e0edbc2b2a52c057375bf301d47da2089c075ff76331d14" ], "index": "pypi", - "version": "==2.0.3" + "version": "==2.1.0" }, "pyasn1": { "hashes": [ @@ -89,25 +89,18 @@ }, "python-ldap": { "hashes": [ - "sha256:4711cacf013e298754abd70058ccc995758177fb425f1c2d30e71adfc1d00aa5" + "sha256:60464c8fc25e71e0fd40449a24eae482dcd0fb7fcf823e7de627a6525b3e0d12" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.3.1" - }, - "pytz": { - "hashes": [ - "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", - "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" - ], - "version": "==2021.1" + "markers": "python_version >= '3.6'", + "version": "==3.4.0" }, "sqlparse": { "hashes": [ - "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", - "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" + "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", + "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" ], "markers": "python_version >= '3.5'", - "version": "==0.4.1" + "version": "==0.4.2" } }, "develop": {} From 4fb8aa74f07e7bc7df7cbb359b6e76c761a6a835 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 3 Feb 2022 13:45:58 -0500 Subject: [PATCH 10/24] Move Members model to new "membershipworks" app --- member_paperwork/settings/base.py | 4 +++- membershipworks/__init__.py | 0 membershipworks/admin.py | 14 ++++++++++++++ membershipworks/apps.py | 6 ++++++ membershipworks/models.py | 18 ++++++++++++++++++ membershipworks/routers.py | 21 +++++++++++++++++++++ membershipworks/tests.py | 3 +++ membershipworks/views.py | 3 +++ paperwork/admin.py | 25 +++++++------------------ paperwork/models.py | 17 +---------------- paperwork/routers.py | 31 +++---------------------------- 11 files changed, 79 insertions(+), 63 deletions(-) create mode 100644 membershipworks/__init__.py create mode 100644 membershipworks/admin.py create mode 100644 membershipworks/apps.py create mode 100644 membershipworks/models.py create mode 100644 membershipworks/routers.py create mode 100644 membershipworks/tests.py create mode 100644 membershipworks/views.py diff --git a/member_paperwork/settings/base.py b/member_paperwork/settings/base.py index 8fffde2..19dd5d1 100644 --- a/member_paperwork/settings/base.py +++ b/member_paperwork/settings/base.py @@ -28,6 +28,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'membershipworks.apps.MembershipworksConfig', 'paperwork.apps.PaperworkConfig', ] @@ -62,7 +63,8 @@ TEMPLATES = [ WSGI_APPLICATION = 'member_paperwork.wsgi.application' -DATABASE_ROUTERS = ['paperwork.routers.MembershipWorksRouter', 'paperwork.routers.PaperworkRouter'] +DATABASE_ROUTERS = ['membershipworks.routers.MembershipWorksRouter', + 'paperwork.routers.PaperworkRouter'] # Internationalization diff --git a/membershipworks/__init__.py b/membershipworks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/membershipworks/admin.py b/membershipworks/admin.py new file mode 100644 index 0000000..7dbdf5a --- /dev/null +++ b/membershipworks/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin + +from .models import Member + + +@admin.register(Member) +class MemberAdmin(admin.ModelAdmin): + readonly_fields = [field.name for field in Member._meta.get_fields() if field.concrete] + + def has_add_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False diff --git a/membershipworks/apps.py b/membershipworks/apps.py new file mode 100644 index 0000000..10a3771 --- /dev/null +++ b/membershipworks/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MembershipworksConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'membershipworks' diff --git a/membershipworks/models.py b/membershipworks/models.py new file mode 100644 index 0000000..1d3a4c4 --- /dev/null +++ b/membershipworks/models.py @@ -0,0 +1,18 @@ +from django.db import models + + +class Member(models.Model): + # TODO: this is only a partial set of fields, may want to expand + uid = models.CharField(primary_key=True, max_length=24) + year_of_birth = models.TextField(db_column='Year of Birth', blank=True, null=True) + account_name = models.TextField(db_column='Account Name', blank=True, null=True) + first_name = models.TextField(db_column='First Name', blank=True, null=True) + last_name = models.TextField(db_column='Last Name', blank=True, null=True) + + def __str__(self): + return f"{self.account_name}" + + class Meta: + managed = False + db_table = 'members' + ordering = ('first_name', 'last_name') diff --git a/membershipworks/routers.py b/membershipworks/routers.py new file mode 100644 index 0000000..d1a80b2 --- /dev/null +++ b/membershipworks/routers.py @@ -0,0 +1,21 @@ +class MembershipWorksRouter: + app_label = 'membershipworks' + db = 'membershipworks' + + def db_for_read(self, model, **hints): + if model._meta.app_label == self.app_label: + return self.db + return None + + def db_for_write(self, model, **hints): + if model._meta.app_label == self.app_label: + return self.db + return None + + def allow_relation(self, obj1, obj2, **hints): + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if db == self.db: + return False + return None diff --git a/membershipworks/tests.py b/membershipworks/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/membershipworks/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/membershipworks/views.py b/membershipworks/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/membershipworks/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/paperwork/admin.py b/paperwork/admin.py index d43ec70..54fb78f 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -2,44 +2,33 @@ from django.contrib import admin from .models import (CmsRedRiverVeteransScholarship, CertificationDefinition, Certification, - InstructorOrVendor, SpecialProgram, Waiver, - Member) + InstructorOrVendor, SpecialProgram, Waiver) +@admin.register(CertificationDefinition) class CertificationDefinitionAdmin(admin.ModelAdmin): search_fields = ['certification_name', 'department'] +@admin.register(Certification) class CertificationAdmin(admin.ModelAdmin): search_fields = ['name', 'certification__certification_name', 'certification__department'] +@admin.register(InstructorOrVendor) class InstructorOrVendorAdmin(admin.ModelAdmin): search_fields = ['name'] +@admin.register(SpecialProgram) class SpecialProgramAdmin(admin.ModelAdmin): search_fields = ['program_name'] +@admin.register(Waiver) class WaiverAdmin(admin.ModelAdmin): search_fields = ['name'] -class MemberAdmin(admin.ModelAdmin): - readonly_fields = [field.name for field in Member._meta.get_fields() if field.concrete] - - def has_add_permission(self, request, obj=None): - return False - - def has_delete_permission(self, request, obj=None): - return False - - admin.site.register(CmsRedRiverVeteransScholarship) -admin.site.register(CertificationDefinition, CertificationDefinitionAdmin) -admin.site.register(Certification, CertificationAdmin) -admin.site.register(InstructorOrVendor, InstructorOrVendorAdmin) -admin.site.register(SpecialProgram, SpecialProgramAdmin) -admin.site.register(Waiver, WaiverAdmin) -admin.site.register(Member, MemberAdmin) + diff --git a/paperwork/models.py b/paperwork/models.py index 03ac08c..171038c 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -1,21 +1,6 @@ from django.db import models - -class Member(models.Model): - # TODO: this is only a partial set of fields, may want to expand - uid = models.CharField(primary_key=True, max_length=24) - year_of_birth = models.TextField(db_column='Year of Birth', blank=True, null=True) - account_name = models.TextField(db_column='Account Name', blank=True, null=True) - first_name = models.TextField(db_column='First Name', blank=True, null=True) - last_name = models.TextField(db_column='Last Name', blank=True, null=True) - - def __str__(self): - return f"{self.account_name}" - - class Meta: - managed = False - db_table = 'members' - ordering = ('first_name', 'last_name') +from membershipworks.models import Member class CmsRedRiverVeteransScholarship(models.Model): diff --git a/paperwork/routers.py b/paperwork/routers.py index 4e2249c..c17d73d 100644 --- a/paperwork/routers.py +++ b/paperwork/routers.py @@ -1,8 +1,6 @@ -from .models import Member - - class PaperworkRouter: app_label = 'paperwork' + related_app_labels = {'paperwork', 'membershipworks'} db = 'cms' def db_for_read(self, model, **hints): @@ -16,8 +14,8 @@ class PaperworkRouter: return None def allow_relation(self, obj1, obj2, **hints): - if (isinstance(obj1, Member) and obj2._meta.app_label == self.app_label) or \ - (isinstance(obj2, Member) and obj1._meta.app_label == self.app_label): + if obj1._meta.app_label in self.related_app_labels or \ + obj2._meta.app_label in self.related_app_labels: return True return None @@ -25,26 +23,3 @@ class PaperworkRouter: if db == self.db: return False return None - - -# TODO: this is pretty hacky/special cased -class MembershipWorksRouter: - db = 'membershipworks' - - def db_for_read(self, model, **hints): - if model == Member: - return self.db - return None - - def db_for_write(self, model, **hints): - if model == Member: - return self.db - return None - - def allow_relation(self, obj1, obj2, **hints): - return None - - def allow_migrate(self, db, app_label, model_name=None, **hints): - if db == self.db: - return False - return None From d34bff860aa662092fde7f495858fe1e619e588e Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 3 Feb 2022 13:54:13 -0500 Subject: [PATCH 11/24] Allow management of all paperwork models --- paperwork/models.py | 6 ------ paperwork/routers.py | 5 ----- 2 files changed, 11 deletions(-) diff --git a/paperwork/models.py b/paperwork/models.py index 171038c..8711282 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -19,7 +19,6 @@ class CmsRedRiverVeteransScholarship(models.Model): return f"{self.program_name} {self.member_name}" class Meta: - managed = False db_table = 'CMS Red River Veterans Scholarship' @@ -32,7 +31,6 @@ class CertificationDefinition(models.Model): return f"{self.certification_name} <{self.department}>" class Meta: - managed = False db_table = 'Certification Definitions' ordering = ('certification_name', 'department') @@ -52,7 +50,6 @@ class Certification(models.Model): return f"{self.name} - {self.certification} [{self.version}]" class Meta: - managed = False db_table = 'Certifications' @@ -68,7 +65,6 @@ class InstructorOrVendor(models.Model): return f"{self.name}" class Meta: - managed = False db_table = 'Instructors and Vendors' @@ -86,7 +82,6 @@ class SpecialProgram(models.Model): return self.program_name class Meta: - managed = False db_table = 'Special_Programs' @@ -105,5 +100,4 @@ class Waiver(models.Model): return f"{self.name} {self.date}" class Meta: - managed = False db_table = 'Waivers' diff --git a/paperwork/routers.py b/paperwork/routers.py index c17d73d..02d52d4 100644 --- a/paperwork/routers.py +++ b/paperwork/routers.py @@ -18,8 +18,3 @@ class PaperworkRouter: obj2._meta.app_label in self.related_app_labels: return True return None - - def allow_migrate(self, db, app_label, model_name=None, **hints): - if db == self.db: - return False - return None From 68a42b90ced9badcd873c3240d061e5aab87b8c3 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 3 Feb 2022 16:17:33 -0500 Subject: [PATCH 12/24] "Fix" foreign key to members table Turning off the constraint here is probably not a good idea in the long term, but it will work until I move tables around to make this sane --- membershipworks/routers.py | 3 --- paperwork/models.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/membershipworks/routers.py b/membershipworks/routers.py index d1a80b2..906607d 100644 --- a/membershipworks/routers.py +++ b/membershipworks/routers.py @@ -12,9 +12,6 @@ class MembershipWorksRouter: return self.db return None - def allow_relation(self, obj1, obj2, **hints): - return None - def allow_migrate(self, db, app_label, model_name=None, **hints): if db == self.db: return False diff --git a/paperwork/models.py b/paperwork/models.py index 8711282..60add71 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -39,8 +39,7 @@ class Certification(models.Model): number = models.AutoField(db_column='Number', primary_key=True) certification = models.ForeignKey(CertificationDefinition, on_delete=models.PROTECT, db_column='Certification') name = models.CharField(db_column='Name', max_length=255) - # TODO: this should be a fk to the membershipworks.members table - member = models.ForeignKey(Member, on_delete=models.PROTECT, to_field='uid', db_column='uid', blank=True, null=True) + member = models.ForeignKey(Member, on_delete=models.PROTECT, to_field='uid', db_column='uid', blank=True, null=True, db_constraint=False) certified_by = models.CharField(db_column='Certified_By', max_length=255, blank=True, null=True) date = models.DateField(db_column='Date', blank=True, null=True) version = models.CharField(db_column='Version', max_length=255, blank=True, null=True) From a892f11c0f43c65921788e132e117041b9e887d4 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 3 Feb 2022 16:19:03 -0500 Subject: [PATCH 13/24] Add intial migration for paperwork --- paperwork/migrations/0001_initial.py | 107 +++++++++++++++++++++++++++ paperwork/migrations/__init__.py | 0 2 files changed, 107 insertions(+) create mode 100644 paperwork/migrations/0001_initial.py create mode 100644 paperwork/migrations/__init__.py diff --git a/paperwork/migrations/0001_initial.py b/paperwork/migrations/0001_initial.py new file mode 100644 index 0000000..72ecdbf --- /dev/null +++ b/paperwork/migrations/0001_initial.py @@ -0,0 +1,107 @@ +# Generated by Django 4.0.2 on 2022-02-03 21:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name='CertificationDefinition', + fields=[ + ('certification_identifier', models.AutoField(db_column='Certification Identifier', primary_key=True, serialize=False)), + ('certification_name', models.CharField(blank=True, db_column='Certification Name', max_length=255, null=True)), + ('department', models.CharField(blank=True, db_column='Department', max_length=255, null=True)), + ], + options={ + 'db_table': 'Certification Definitions', + 'ordering': ('certification_name', 'department'), + }, + ), + migrations.CreateModel( + name='CmsRedRiverVeteransScholarship', + fields=[ + ('serial', models.AutoField(primary_key=True, serialize=False)), + ('program_name', models.CharField(db_column='Program Name', max_length=255)), + ('member_name', models.CharField(blank=True, db_column='Member Name', max_length=255, null=True)), + ('discount_percent', models.DecimalField(blank=True, db_column='Discount Percent', decimal_places=0, max_digits=16, null=True)), + ('discount_code', models.CharField(blank=True, db_column='Discount Code', max_length=255, null=True)), + ('membership_code', models.CharField(blank=True, db_column='Membership Code', max_length=255, null=True)), + ('start_date', models.DateField(blank=True, db_column='Start Date', null=True)), + ('end_date', models.DateField(blank=True, db_column='End Date', null=True)), + ('program_amount', models.DecimalField(blank=True, db_column='Program Amount', decimal_places=0, max_digits=16, null=True)), + ('program_status', models.CharField(blank=True, db_column='Program Status', max_length=16, null=True)), + ], + options={ + 'db_table': 'CMS Red River Veterans Scholarship', + }, + ), + migrations.CreateModel( + name='InstructorOrVendor', + fields=[ + ('serial', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(db_column='Name', max_length=255)), + ('instructor_agreement_date', models.DateField(blank=True, db_column='Instructor Agreement Date', null=True)), + ('w9_date', models.DateField(blank=True, db_column='W9 date', null=True)), + ('phone', models.CharField(blank=True, max_length=255, null=True)), + ('email_address', models.CharField(blank=True, db_column='email address', max_length=255, null=True)), + ], + options={ + 'db_table': 'Instructors and Vendors', + }, + ), + migrations.CreateModel( + name='SpecialProgram', + fields=[ + ('program_name', models.CharField(db_column='Program Name', max_length=255, primary_key=True, serialize=False)), + ('discount_percent', models.DecimalField(blank=True, db_column='Discount Percent', decimal_places=0, max_digits=16, null=True)), + ('discount_code', models.CharField(blank=True, db_column='Discount Code', max_length=255, null=True)), + ('membership_code', models.CharField(blank=True, db_column='Membership Code', max_length=255, null=True)), + ('start_date', models.DateField(blank=True, db_column='Start Date', null=True)), + ('end_date', models.DateField(blank=True, db_column='End Date', null=True)), + ('program_amount', models.DecimalField(blank=True, db_column='Program Amount', decimal_places=0, max_digits=16, null=True)), + ('program_status', models.CharField(blank=True, db_column='Program Status', max_length=16, null=True)), + ], + options={ + 'db_table': 'Special_Programs', + }, + ), + migrations.CreateModel( + name='Waiver', + fields=[ + ('number', models.AutoField(db_column='Number', primary_key=True, serialize=False)), + ('name', models.CharField(db_column='Name', max_length=255)), + ('date', models.DateField(db_column='Date')), + ('emergency_contact_name', models.CharField(blank=True, db_column='Emergency Contact Name', max_length=255, null=True)), + ('emergency_contact_number', models.CharField(blank=True, db_column='Emergency Contact Number', max_length=25, null=True)), + ('waiver_version', models.CharField(db_column='Waiver version', max_length=64)), + ('guardian_name', models.CharField(blank=True, db_column='Guardian Name', max_length=255, null=True)), + ('guardian_relation', models.CharField(blank=True, db_column='Guardian Relation', max_length=255, null=True)), + ('guardian_date', models.DateField(blank=True, db_column='Guardian Date', null=True)), + ], + options={ + 'db_table': 'Waivers', + }, + ), + migrations.CreateModel( + name='Certification', + fields=[ + ('number', models.AutoField(db_column='Number', primary_key=True, serialize=False)), + ('name', models.CharField(db_column='Name', max_length=255)), + ('certified_by', models.CharField(blank=True, db_column='Certified_By', max_length=255, null=True)), + ('date', models.DateField(blank=True, db_column='Date', null=True)), + ('version', models.CharField(blank=True, db_column='Version', max_length=255, null=True)), + ('shop_lead_notified', models.DateTimeField(blank=True, db_column='Shop Lead Notified', null=True)), + ('certification', models.ForeignKey(db_column='Certification', on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationdefinition')), + ('member', models.ForeignKey(blank=True, db_column='uid', db_constraint=False, null=True, on_delete=django.db.models.deletion.PROTECT, to='membershipworks.member')), + ], + options={ + 'db_table': 'Certifications', + }, + ), + ] diff --git a/paperwork/migrations/__init__.py b/paperwork/migrations/__init__.py new file mode 100644 index 0000000..e69de29 From 030cbe8a92cdb4136fa8f3159e785d1d1d13c856 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 3 Feb 2022 16:36:37 -0500 Subject: [PATCH 14/24] Use autocomplete for member field in Certification admin --- membershipworks/admin.py | 1 + paperwork/admin.py | 1 + 2 files changed, 2 insertions(+) diff --git a/membershipworks/admin.py b/membershipworks/admin.py index 7dbdf5a..6fb7f28 100644 --- a/membershipworks/admin.py +++ b/membershipworks/admin.py @@ -6,6 +6,7 @@ from .models import Member @admin.register(Member) class MemberAdmin(admin.ModelAdmin): readonly_fields = [field.name for field in Member._meta.get_fields() if field.concrete] + search_fields = ['account_name'] def has_add_permission(self, request, obj=None): return False diff --git a/paperwork/admin.py b/paperwork/admin.py index 54fb78f..a2bdbb7 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -13,6 +13,7 @@ class CertificationDefinitionAdmin(admin.ModelAdmin): @admin.register(Certification) class CertificationAdmin(admin.ModelAdmin): search_fields = ['name', 'certification__certification_name', 'certification__department'] + autocomplete_fields = ['member'] @admin.register(InstructorOrVendor) From 4ac3fd180647e78e3ec9d82d3da0ed99c7fa8e22 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Fri, 4 Feb 2022 11:12:11 -0500 Subject: [PATCH 15/24] Add django-admin-logs app for viewing admin history --- Pipfile | 1 + Pipfile.lock | 10 +++++++++- member_paperwork/settings/base.py | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e31c80c..5c5bbd4 100644 --- a/Pipfile +++ b/Pipfile @@ -7,6 +7,7 @@ name = "pypi" django = "~=4.0" mysqlclient = "~=2.1" django-auth-ldap = "~=4.0" +django-admin-logs = "~=1.0" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 90fe008..edaa57b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "64d22e44b33d1417c5433e61f41e79967fa16dfd4f2faa5787724bc0760f5f8c" + "sha256": "19857e48b0277129d8fe2499742545bbb58183069d59946c495ede86bc367bea" }, "pipfile-spec": 6, "requires": { @@ -32,6 +32,14 @@ "index": "pypi", "version": "==4.0.2" }, + "django-admin-logs": { + "hashes": [ + "sha256:81753c20d372bc5562fe4a09090418bbb61b308388e851b19192873a472fa3d1", + "sha256:aedb5df940d32c10423d65136343bc009727df8a5a49ed0196e65241d823a890" + ], + "index": "pypi", + "version": "==1.0.2" + }, "django-auth-ldap": { "hashes": [ "sha256:276f79e624ce083ce13f161387f65ff1c0efe83ef8a42f2b9830d43317b15239", diff --git a/member_paperwork/settings/base.py b/member_paperwork/settings/base.py index 19dd5d1..34044e5 100644 --- a/member_paperwork/settings/base.py +++ b/member_paperwork/settings/base.py @@ -28,6 +28,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_admin_logs', 'membershipworks.apps.MembershipworksConfig', 'paperwork.apps.PaperworkConfig', ] From da8417c12b1b4c277b860388e997919ad87dedbf Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Fri, 4 Feb 2022 15:44:10 -0500 Subject: [PATCH 16/24] Define DEFAULT_AUTO_FIELD --- member_paperwork/settings/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/member_paperwork/settings/base.py b/member_paperwork/settings/base.py index 34044e5..895a5ec 100644 --- a/member_paperwork/settings/base.py +++ b/member_paperwork/settings/base.py @@ -63,6 +63,7 @@ TEMPLATES = [ WSGI_APPLICATION = 'member_paperwork.wsgi.application' +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DATABASE_ROUTERS = ['membershipworks.routers.MembershipWorksRouter', 'paperwork.routers.PaperworkRouter'] From a586688653150520f3c6997cd9f1a240b215e1ae Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Fri, 4 Feb 2022 16:30:52 -0500 Subject: [PATCH 17/24] Add Certification Version model, moving existing data to it --- paperwork/admin.py | 12 ++++ .../0002_add_certification_version_model.py | 60 +++++++++++++++++++ paperwork/models.py | 20 ++++++- 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 paperwork/migrations/0002_add_certification_version_model.py diff --git a/paperwork/admin.py b/paperwork/admin.py index a2bdbb7..1b9495a 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -2,12 +2,24 @@ from django.contrib import admin from .models import (CmsRedRiverVeteransScholarship, CertificationDefinition, Certification, + CertificationVersion, InstructorOrVendor, SpecialProgram, Waiver) +class CertificationVersionInline(admin.TabularInline): + model = CertificationVersion + extra = 1 + + +@admin.register(CertificationVersion) +class CertificationVersionAdmin(admin.ModelAdmin): + search_fields = ['definition__certification_name', 'version'] + + @admin.register(CertificationDefinition) class CertificationDefinitionAdmin(admin.ModelAdmin): search_fields = ['certification_name', 'department'] + inlines = [CertificationVersionInline] @admin.register(Certification) diff --git a/paperwork/migrations/0002_add_certification_version_model.py b/paperwork/migrations/0002_add_certification_version_model.py new file mode 100644 index 0000000..da8d8d5 --- /dev/null +++ b/paperwork/migrations/0002_add_certification_version_model.py @@ -0,0 +1,60 @@ +# (Partially) Generated by Django 4.0.2 on 2022-02-04 18:01 + +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_certification_version_forward(apps, schema_editor): + db_alias = schema_editor.connection.alias + + Certification = apps.get_model("paperwork", "Certification") + CertificationVersion = apps.get_model("paperwork", "CertificationVersion") + + for certification in Certification.objects.using(db_alias).all(): + (version, _) = CertificationVersion.objects.using(db_alias).get_or_create( + definition=certification.certification, version=certification.version + ) + certification.certification_version = version + certification.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('membershipworks', '0001_initial'), + ('paperwork', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='CertificationVersion', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('version', models.CharField(blank=True, db_column='Version', max_length=255, null=True)), + ('definition', models.ForeignKey(db_column='Certification', on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationdefinition')), + ], + ), + migrations.AddConstraint( + model_name='certificationversion', + constraint=models.UniqueConstraint(fields=('definition', 'version'), name='unique_certification_version'), + ), + migrations.AddField( + model_name='certification', + name='certification_version', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationversion', null=True), + ), + migrations.RunPython(migrate_certification_version_forward), + migrations.AlterField( + model_name='certification', + name='certification_version', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationversion'), + ), + migrations.RemoveField( + model_name='certification', + name='certification', + ), + migrations.RemoveField( + model_name='certification', + name='version', + ), + ] diff --git a/paperwork/models.py b/paperwork/models.py index 60add71..1c25e8b 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -35,18 +35,32 @@ class CertificationDefinition(models.Model): ordering = ('certification_name', 'department') +class CertificationVersion(models.Model): + definition = models.ForeignKey(CertificationDefinition, on_delete=models.PROTECT, db_column='Certification') + version = models.CharField(db_column='Version', max_length=255, blank=True, null=True) + + def __str__(self): + return f"{self.definition} [{self.version}]" + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["definition", "version"], name="unique_certification_version" + ) + ] + + class Certification(models.Model): number = models.AutoField(db_column='Number', primary_key=True) - certification = models.ForeignKey(CertificationDefinition, on_delete=models.PROTECT, db_column='Certification') + certification_version = models.ForeignKey(CertificationVersion, on_delete=models.PROTECT) name = models.CharField(db_column='Name', max_length=255) member = models.ForeignKey(Member, on_delete=models.PROTECT, to_field='uid', db_column='uid', blank=True, null=True, db_constraint=False) certified_by = models.CharField(db_column='Certified_By', max_length=255, blank=True, null=True) date = models.DateField(db_column='Date', blank=True, null=True) - version = models.CharField(db_column='Version', max_length=255, blank=True, null=True) shop_lead_notified = models.DateTimeField(db_column='Shop Lead Notified', blank=True, null=True) def __str__(self): - return f"{self.name} - {self.certification} [{self.version}]" + return f"{self.name} - {self.certification_version}" class Meta: db_table = 'Certifications' From 81d12f86a9170a781c7631d5a14566b2564d87df Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Fri, 4 Feb 2022 17:14:17 -0500 Subject: [PATCH 18/24] Add missing `notes` field to Certifications --- paperwork/migrations/0001_initial.py | 1 + paperwork/models.py | 1 + 2 files changed, 2 insertions(+) diff --git a/paperwork/migrations/0001_initial.py b/paperwork/migrations/0001_initial.py index 72ecdbf..ee67f2e 100644 --- a/paperwork/migrations/0001_initial.py +++ b/paperwork/migrations/0001_initial.py @@ -99,6 +99,7 @@ class Migration(migrations.Migration): ('shop_lead_notified', models.DateTimeField(blank=True, db_column='Shop Lead Notified', null=True)), ('certification', models.ForeignKey(db_column='Certification', on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationdefinition')), ('member', models.ForeignKey(blank=True, db_column='uid', db_constraint=False, null=True, on_delete=django.db.models.deletion.PROTECT, to='membershipworks.member')), + ('notes', models.CharField(blank=True, db_column='Notes', max_length=255, null=True)), ], options={ 'db_table': 'Certifications', diff --git a/paperwork/models.py b/paperwork/models.py index 1c25e8b..12202e2 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -58,6 +58,7 @@ class Certification(models.Model): certified_by = models.CharField(db_column='Certified_By', max_length=255, blank=True, null=True) date = models.DateField(db_column='Date', blank=True, null=True) shop_lead_notified = models.DateTimeField(db_column='Shop Lead Notified', blank=True, null=True) + notes = models.CharField(db_column='Notes', max_length=255, blank=True, null=True) def __str__(self): return f"{self.name} - {self.certification_version}" From 9997c129ff68c9f8c8b0fe2b71d1612529032bfc Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Sat, 5 Feb 2022 00:28:58 -0500 Subject: [PATCH 19/24] Add some display and filtering options to paperwork admin --- paperwork/admin.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/paperwork/admin.py b/paperwork/admin.py index 1b9495a..209a74b 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -14,11 +14,15 @@ class CertificationVersionInline(admin.TabularInline): @admin.register(CertificationVersion) class CertificationVersionAdmin(admin.ModelAdmin): search_fields = ['definition__certification_name', 'version'] + list_display = ['definition', 'version'] + list_filter = ['definition__department', 'definition__certification_name'] @admin.register(CertificationDefinition) class CertificationDefinitionAdmin(admin.ModelAdmin): - search_fields = ['certification_name', 'department'] + search_fields = ['certification_name'] + list_display = ['certification_name', 'department'] + list_filter = ['department'] inlines = [CertificationVersionInline] @@ -26,6 +30,40 @@ class CertificationDefinitionAdmin(admin.ModelAdmin): class CertificationAdmin(admin.ModelAdmin): search_fields = ['name', 'certification__certification_name', 'certification__department'] autocomplete_fields = ['member'] + exclude = ['shop_lead_notified'] + + @admin.display(description='Certification Name', + ordering='certification_version__definition__certification_name') + def certification_name(self, obj): + return obj.certification_version.definition.certification_name + + @admin.display(description='Certification Version', + ordering='certification_version__version') + def certification_version_version(self, obj): + return obj.certification_version.version + + @admin.display(description='Department', + ordering='certification_version__definition__department') + def certification_department(self, obj): + return obj.certification_version.definition.department + + list_display = [ + 'certification_name', + 'name', + 'certification_version_version', + 'certification_department', + 'date', + 'shop_lead_notified', + 'certified_by', + ] + list_display_links = [ + 'certification_name', + 'name', + ] + list_filter = [ + 'certification_version__definition__department', + ('shop_lead_notified', admin.EmptyFieldListFilter), + ] @admin.register(InstructorOrVendor) From d50cec383e703e46b3a118270175a5256780df04 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Thu, 10 Feb 2022 16:51:32 -0500 Subject: [PATCH 20/24] Add complete Member, Flag, and MemberFlag models --- membershipworks/admin.py | 26 +++++-- membershipworks/models.py | 142 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 156 insertions(+), 12 deletions(-) diff --git a/membershipworks/admin.py b/membershipworks/admin.py index 6fb7f28..7a3d1bd 100644 --- a/membershipworks/admin.py +++ b/membershipworks/admin.py @@ -1,15 +1,29 @@ from django.contrib import admin -from .models import Member +from .models import Member, Flag -@admin.register(Member) -class MemberAdmin(admin.ModelAdmin): - readonly_fields = [field.name for field in Member._meta.get_fields() if field.concrete] - search_fields = ['account_name'] - +class ReadOnlyAdmin(admin.ModelAdmin): def has_add_permission(self, request, obj=None): return False + def has_change_permission(self, request, obj=None): + return False + def has_delete_permission(self, request, obj=None): return False + + +class MemberFlagInline(admin.TabularInline): + model = Member.flags.through + + +@admin.register(Member) +class MemberAdmin(ReadOnlyAdmin): + search_fields = ['account_name'] + inlines = [MemberFlagInline] + + +@admin.register(Flag) +class FlagAdmin(ReadOnlyAdmin): + inlines = [MemberFlagInline] diff --git a/membershipworks/models.py b/membershipworks/models.py index 1d3a4c4..c3641c5 100644 --- a/membershipworks/models.py +++ b/membershipworks/models.py @@ -1,13 +1,127 @@ +from datetime import datetime + from django.db import models +class Flag(models.Model): + id = models.CharField(max_length=24, primary_key=True) + name = models.TextField(null=True) + type = models.CharField(max_length=6) + + def __str__(self): + return f'{self.name} ({self.type})' + + class Meta: + managed = False + db_table = 'flag' + + +# TODO: is this still a temporal table? class Member(models.Model): - # TODO: this is only a partial set of fields, may want to expand - uid = models.CharField(primary_key=True, max_length=24) - year_of_birth = models.TextField(db_column='Year of Birth', blank=True, null=True) - account_name = models.TextField(db_column='Account Name', blank=True, null=True) - first_name = models.TextField(db_column='First Name', blank=True, null=True) - last_name = models.TextField(db_column='Last Name', blank=True, null=True) + uid = models.CharField(max_length=24, primary_key=True) + year_of_birth = models.TextField(db_column="Year of Birth", null=True) + account_name = models.TextField(db_column="Account Name", null=True) + first_name = models.TextField(db_column="First Name", null=True) + last_name = models.TextField(db_column="Last Name", null=True) + phone = models.TextField(db_column="Phone", null=True) + email = models.TextField(db_column="Email", null=True) + address_street = models.TextField(db_column="Address (Street)", null=True) + address_city = models.TextField(db_column="Address (City)", null=True) + address_state_province = models.TextField( + db_column="Address (State/Province)", null=True + ) + address_postal_code = models.TextField(db_column="Address (Postal Code)", null=True) + address_country = models.TextField(db_column="Address (Country)", null=True) + profile_description = models.TextField(db_column="Profile description", null=True) + website = models.TextField(db_column="Website", null=True) + fax = models.TextField(db_column="Fax", null=True) + contact_person = models.TextField(db_column="Contact Person", null=True) + password = models.TextField(db_column="Password", null=True) + position_relation = models.TextField(db_column="Position/relation", null=True) + parent_account_id = models.TextField(db_column="Parent Account ID", null=True) + gift_membership_purchased_by = models.TextField( + db_column="Gift Membership purchased by", null=True + ) + purchased_gift_membership_for = models.TextField( + db_column="Purchased Gift Membership for", null=True + ) + closet_storage = models.TextField(db_column="Closet Storage #", null=True) + storage_shelf = models.TextField(db_column="Storage Shelf #", null=True) + personal_studio_space = models.TextField(db_column="Personal Studio Space #", null=True) + access_permitted_shops_during_extended_hours = models.BooleanField( + db_column="Access Permitted Shops During Extended Hours?" + ) + normal_access_permitted_during_covid19_limited_operations = models.BooleanField( + db_column="Normal Access Permitted During COVID-19 Limited Operations" + ) + access_permitted_during_covid19_staffed_period_only = models.BooleanField( + db_column="Access Permitted During COVID-19 Staffed Period Only" + ) + access_front_door_and_studio_space_during_extended_hours = models.BooleanField( + db_column="Access Front Door and Studio Space During Extended Hours?" + ) + access_wood_shop = models.BooleanField(db_column="Access Wood Shop?") + access_metal_shop = models.BooleanField(db_column="Access Metal Shop?") + access_storage_closet = models.BooleanField(db_column="Access Storage Closet?") + access_studio_space = models.BooleanField(db_column="Access Studio Space?") + access_front_door = models.BooleanField(db_column="Access Front Door?") + access_card_number = models.TextField(db_column="Access Card Number", null=True) + access_card_facility_code = models.TextField( + db_column="Access Card Facility Code", null=True + ) + auto_billing_id = models.TextField(db_column="Auto Billing ID", null=True) + billing_method = models.TextField(db_column="Billing Method", null=True) + renewal_date = models.DateField(db_column="Renewal Date", null=True) + join_date = models.DateField(db_column="Join Date", null=True) + admin_note = models.TextField(db_column="Admin note", null=True) + profile_gallery_image_url = models.TextField( + db_column="Profile gallery image URL", null=True + ) + business_card_image_url = models.TextField( + db_column="Business card image URL", null=True + ) + instagram = models.TextField(db_column="Instagram", null=True) + pinterest = models.TextField(db_column="Pinterest", null=True) + youtube = models.TextField(db_column="Youtube", null=True) + yelp = models.TextField(db_column="Yelp", null=True) + google = models.TextField(db_column="Google+", null=True) + bbb = models.TextField(db_column="BBB", null=True) + twitter = models.TextField(db_column="Twitter", null=True) + facebook = models.TextField(db_column="Facebook", null=True) + linked_in = models.TextField(db_column="LinkedIn", null=True) + do_not_show_street_address_in_profile = models.TextField( + db_column="Do not show street address in profile", null=True + ) + do_not_list_in_directory = models.TextField( + db_column="Do not list in directory", null=True + ) + how_did_you_hear = models.TextField(db_column="HowDidYouHear", null=True) + authorize_charge = models.TextField(db_column="authorizeCharge", null=True) + policy_agreement = models.TextField(db_column="policyAgreement", null=True) + waiver_form_signed_and_on_file_date = models.DateField( + db_column="Waiver form signed and on file date.", null=True + ) + membership_agreement_signed_and_on_file_date = models.DateField( + db_column="Membership Agreement signed and on file date.", null=True + ) + ip_address = models.TextField(db_column="IP Address", null=True) + audit_date = models.DateField(db_column="Audit Date", null=True) + agreement_version = models.TextField(db_column="Agreement Version", null=True) + paperwork_status = models.TextField(db_column="Paperwork status", null=True) + membership_agreement_dated = models.BooleanField(db_column="Membership agreement dated") + membership_agreement_acknowledgement_page_filled_out = models.BooleanField( + db_column="Membership Agreement Acknowledgement Page Filled Out" + ) + membership_agreement_signed = models.BooleanField( + db_column="Membership Agreement Signed" + ) + liability_form_filled_out = models.BooleanField(db_column="Liability Form Filled Out") + self_certify_essential_business = models.BooleanField( + db_column="selfCertifyEssentialBusiness" + ) + accepted_covid19_policy = models.BooleanField(db_column="Accepted COVID-19 Policy") + flags = models.ManyToManyField(Flag, through='MemberFlag', related_name='members') + def __str__(self): return f"{self.account_name}" @@ -16,3 +130,19 @@ class Member(models.Model): managed = False db_table = 'members' ordering = ('first_name', 'last_name') + + +class MemberFlag(models.Model): + member = models.ForeignKey(Member, on_delete=models.PROTECT, db_column="uid") + flag = models.ForeignKey(Flag, on_delete=models.PROTECT) + + def __str__(self): + return f'{self.member} - {self.flag}' + + class Meta: + managed = False + db_table = 'memberflag' + constraints = [ + models.UniqueConstraint(fields=['member', 'flag_id'], name='unique_member_flag') + ] + From 35f714760dec3b7660ecac8af079d8819516f444 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Fri, 11 Feb 2022 13:48:47 -0500 Subject: [PATCH 21/24] Add/apply black formatter --- manage.py | 4 +- member_paperwork/asgi.py | 2 +- member_paperwork/settings/base.py | 68 ++-- member_paperwork/settings/prod_base.py | 21 +- member_paperwork/urls.py | 2 +- member_paperwork/wsgi.py | 2 +- membershipworks/admin.py | 2 +- membershipworks/apps.py | 4 +- membershipworks/models.py | 32 +- membershipworks/routers.py | 4 +- paperwork/admin.py | 81 +++-- paperwork/apps.py | 2 +- paperwork/migrations/0001_initial.py | 331 +++++++++++++++--- .../0002_add_certification_version_model.py | 65 +++- paperwork/models.py | 176 +++++++--- paperwork/routers.py | 12 +- pyproject.toml | 1 + 17 files changed, 577 insertions(+), 232 deletions(-) create mode 100644 pyproject.toml diff --git a/manage.py b/manage.py index 2e04034..d871742 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'member_paperwork.settings.dev') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "member_paperwork.settings.dev") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/member_paperwork/asgi.py b/member_paperwork/asgi.py index c40b727..319c77f 100644 --- a/member_paperwork/asgi.py +++ b/member_paperwork/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'member_paperwork.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "member_paperwork.settings") application = get_asgi_application() diff --git a/member_paperwork/settings/base.py b/member_paperwork/settings/base.py index 895a5ec..0b3afc4 100644 --- a/member_paperwork/settings/base.py +++ b/member_paperwork/settings/base.py @@ -22,58 +22,60 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django_admin_logs', - 'membershipworks.apps.MembershipworksConfig', - 'paperwork.apps.PaperworkConfig', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_admin_logs", + "membershipworks.apps.MembershipworksConfig", + "paperwork.apps.PaperworkConfig", ] 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', + "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 = 'member_paperwork.urls' +ROOT_URLCONF = "member_paperwork.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', + "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 = 'member_paperwork.wsgi.application' +WSGI_APPLICATION = "member_paperwork.wsgi.application" -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" -DATABASE_ROUTERS = ['membershipworks.routers.MembershipWorksRouter', - 'paperwork.routers.PaperworkRouter'] +DATABASE_ROUTERS = [ + "membershipworks.routers.MembershipWorksRouter", + "paperwork.routers.PaperworkRouter", +] # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' +LANGUAGE_CODE = "en-us" +TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True @@ -82,4 +84,4 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" diff --git a/member_paperwork/settings/prod_base.py b/member_paperwork/settings/prod_base.py index 2df0dde..fee4566 100644 --- a/member_paperwork/settings/prod_base.py +++ b/member_paperwork/settings/prod_base.py @@ -11,31 +11,32 @@ DEBUG = False # "AUTH_LDAP_SERVER_URI", "AUTH_LDAP_BIND_DN", and "AUTH_LDAP_BIND_PASSWORD" set in prod.py AUTHENTICATION_BACKENDS = [ - 'django_auth_ldap.backend.LDAPBackend', - 'django.contrib.auth.backends.ModelBackend', + "django_auth_ldap.backend.LDAPBackend", + "django.contrib.auth.backends.ModelBackend", ] AUTH_LDAP_USER_SEARCH = LDAPSearch( - 'cn=users,dc=sawtooth,dc=claremontmakerspace,dc=org', + "cn=users,dc=sawtooth,dc=claremontmakerspace,dc=org", ldap.SCOPE_SUBTREE, - '(uid=%(user)s)', + "(uid=%(user)s)", ) AUTH_LDAP_USER_ATTR_MAP = { - 'first_name': 'givenName', - 'last_name': 'sn', - 'email': 'mail', + "first_name": "givenName", + "last_name": "sn", + "email": "mail", } AUTH_LDAP_USER_FLAGS_BY_GROUP = { "is_staff": LDAPGroupQuery( - "cn=MW_CMS Staff,cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org"), + "cn=MW_CMS Staff,cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org" + ), } AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - 'cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org', + "cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org", ldap.SCOPE_SUBTREE, - '(objectClass=posixGroup)', + "(objectClass=posixGroup)", ) AUTH_LDAP_GROUP_TYPE = PosixGroupType() AUTH_LDAP_MIRROR_GROUPS = True diff --git a/member_paperwork/urls.py b/member_paperwork/urls.py index f1b9910..3aafed5 100644 --- a/member_paperwork/urls.py +++ b/member_paperwork/urls.py @@ -17,5 +17,5 @@ from django.contrib import admin from django.urls import path urlpatterns = [ - path('admin/', admin.site.urls), + path("admin/", admin.site.urls), ] diff --git a/member_paperwork/wsgi.py b/member_paperwork/wsgi.py index 1f29a41..39eccf4 100644 --- a/member_paperwork/wsgi.py +++ b/member_paperwork/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'member_paperwork.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "member_paperwork.settings") application = get_wsgi_application() diff --git a/membershipworks/admin.py b/membershipworks/admin.py index 7a3d1bd..67dea7a 100644 --- a/membershipworks/admin.py +++ b/membershipworks/admin.py @@ -20,7 +20,7 @@ class MemberFlagInline(admin.TabularInline): @admin.register(Member) class MemberAdmin(ReadOnlyAdmin): - search_fields = ['account_name'] + search_fields = ["account_name"] inlines = [MemberFlagInline] diff --git a/membershipworks/apps.py b/membershipworks/apps.py index 10a3771..191218a 100644 --- a/membershipworks/apps.py +++ b/membershipworks/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class MembershipworksConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'membershipworks' + default_auto_field = "django.db.models.BigAutoField" + name = "membershipworks" diff --git a/membershipworks/models.py b/membershipworks/models.py index c3641c5..6f2b154 100644 --- a/membershipworks/models.py +++ b/membershipworks/models.py @@ -9,11 +9,11 @@ class Flag(models.Model): type = models.CharField(max_length=6) def __str__(self): - return f'{self.name} ({self.type})' + return f"{self.name} ({self.type})" class Meta: managed = False - db_table = 'flag' + db_table = "flag" # TODO: is this still a temporal table? @@ -47,7 +47,9 @@ class Member(models.Model): ) closet_storage = models.TextField(db_column="Closet Storage #", null=True) storage_shelf = models.TextField(db_column="Storage Shelf #", null=True) - personal_studio_space = models.TextField(db_column="Personal Studio Space #", null=True) + personal_studio_space = models.TextField( + db_column="Personal Studio Space #", null=True + ) access_permitted_shops_during_extended_hours = models.BooleanField( db_column="Access Permitted Shops During Extended Hours?" ) @@ -108,28 +110,31 @@ class Member(models.Model): audit_date = models.DateField(db_column="Audit Date", null=True) agreement_version = models.TextField(db_column="Agreement Version", null=True) paperwork_status = models.TextField(db_column="Paperwork status", null=True) - membership_agreement_dated = models.BooleanField(db_column="Membership agreement dated") + membership_agreement_dated = models.BooleanField( + db_column="Membership agreement dated" + ) membership_agreement_acknowledgement_page_filled_out = models.BooleanField( db_column="Membership Agreement Acknowledgement Page Filled Out" ) membership_agreement_signed = models.BooleanField( db_column="Membership Agreement Signed" ) - liability_form_filled_out = models.BooleanField(db_column="Liability Form Filled Out") + liability_form_filled_out = models.BooleanField( + db_column="Liability Form Filled Out" + ) self_certify_essential_business = models.BooleanField( db_column="selfCertifyEssentialBusiness" ) accepted_covid19_policy = models.BooleanField(db_column="Accepted COVID-19 Policy") - flags = models.ManyToManyField(Flag, through='MemberFlag', related_name='members') - + flags = models.ManyToManyField(Flag, through="MemberFlag", related_name="members") def __str__(self): return f"{self.account_name}" class Meta: managed = False - db_table = 'members' - ordering = ('first_name', 'last_name') + db_table = "members" + ordering = ("first_name", "last_name") class MemberFlag(models.Model): @@ -137,12 +142,13 @@ class MemberFlag(models.Model): flag = models.ForeignKey(Flag, on_delete=models.PROTECT) def __str__(self): - return f'{self.member} - {self.flag}' + return f"{self.member} - {self.flag}" class Meta: managed = False - db_table = 'memberflag' + db_table = "memberflag" constraints = [ - models.UniqueConstraint(fields=['member', 'flag_id'], name='unique_member_flag') + models.UniqueConstraint( + fields=["member", "flag_id"], name="unique_member_flag" + ) ] - diff --git a/membershipworks/routers.py b/membershipworks/routers.py index 906607d..29f8e61 100644 --- a/membershipworks/routers.py +++ b/membershipworks/routers.py @@ -1,6 +1,6 @@ class MembershipWorksRouter: - app_label = 'membershipworks' - db = 'membershipworks' + app_label = "membershipworks" + db = "membershipworks" def db_for_read(self, model, **hints): if model._meta.app_label == self.app_label: diff --git a/paperwork/admin.py b/paperwork/admin.py index 209a74b..6fe9f24 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -1,9 +1,14 @@ from django.contrib import admin -from .models import (CmsRedRiverVeteransScholarship, - CertificationDefinition, Certification, - CertificationVersion, - InstructorOrVendor, SpecialProgram, Waiver) +from .models import ( + CmsRedRiverVeteransScholarship, + CertificationDefinition, + Certification, + CertificationVersion, + InstructorOrVendor, + SpecialProgram, + Waiver, +) class CertificationVersionInline(admin.TabularInline): @@ -13,73 +18,81 @@ class CertificationVersionInline(admin.TabularInline): @admin.register(CertificationVersion) class CertificationVersionAdmin(admin.ModelAdmin): - search_fields = ['definition__certification_name', 'version'] - list_display = ['definition', 'version'] - list_filter = ['definition__department', 'definition__certification_name'] + search_fields = ["definition__certification_name", "version"] + list_display = ["definition", "version"] + list_filter = ["definition__department", "definition__certification_name"] @admin.register(CertificationDefinition) class CertificationDefinitionAdmin(admin.ModelAdmin): - search_fields = ['certification_name'] - list_display = ['certification_name', 'department'] - list_filter = ['department'] + search_fields = ["certification_name"] + list_display = ["certification_name", "department"] + list_filter = ["department"] inlines = [CertificationVersionInline] @admin.register(Certification) class CertificationAdmin(admin.ModelAdmin): - search_fields = ['name', 'certification__certification_name', 'certification__department'] - autocomplete_fields = ['member'] - exclude = ['shop_lead_notified'] + search_fields = [ + "name", + "certification__certification_name", + "certification__department", + ] + autocomplete_fields = ["member"] + exclude = ["shop_lead_notified"] - @admin.display(description='Certification Name', - ordering='certification_version__definition__certification_name') + @admin.display( + description="Certification Name", + ordering="certification_version__definition__certification_name", + ) def certification_name(self, obj): return obj.certification_version.definition.certification_name - @admin.display(description='Certification Version', - ordering='certification_version__version') + @admin.display( + description="Certification Version", ordering="certification_version__version" + ) def certification_version_version(self, obj): return obj.certification_version.version - @admin.display(description='Department', - ordering='certification_version__definition__department') + @admin.display( + description="Department", + ordering="certification_version__definition__department", + ) def certification_department(self, obj): return obj.certification_version.definition.department list_display = [ - 'certification_name', - 'name', - 'certification_version_version', - 'certification_department', - 'date', - 'shop_lead_notified', - 'certified_by', + "certification_name", + "name", + "certification_version_version", + "certification_department", + "date", + "shop_lead_notified", + "certified_by", ] list_display_links = [ - 'certification_name', - 'name', + "certification_name", + "name", ] list_filter = [ - 'certification_version__definition__department', - ('shop_lead_notified', admin.EmptyFieldListFilter), + "certification_version__definition__department", + ("shop_lead_notified", admin.EmptyFieldListFilter), ] @admin.register(InstructorOrVendor) class InstructorOrVendorAdmin(admin.ModelAdmin): - search_fields = ['name'] + search_fields = ["name"] @admin.register(SpecialProgram) class SpecialProgramAdmin(admin.ModelAdmin): - search_fields = ['program_name'] + search_fields = ["program_name"] @admin.register(Waiver) class WaiverAdmin(admin.ModelAdmin): - search_fields = ['name'] + search_fields = ["name"] admin.site.register(CmsRedRiverVeteransScholarship) - diff --git a/paperwork/apps.py b/paperwork/apps.py index 2bdd60a..edf8c86 100644 --- a/paperwork/apps.py +++ b/paperwork/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class PaperworkConfig(AppConfig): - name = 'paperwork' + name = "paperwork" diff --git a/paperwork/migrations/0001_initial.py b/paperwork/migrations/0001_initial.py index ee67f2e..5dd5dc9 100644 --- a/paperwork/migrations/0001_initial.py +++ b/paperwork/migrations/0001_initial.py @@ -12,97 +12,312 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='CertificationDefinition', + name="CertificationDefinition", fields=[ - ('certification_identifier', models.AutoField(db_column='Certification Identifier', primary_key=True, serialize=False)), - ('certification_name', models.CharField(blank=True, db_column='Certification Name', max_length=255, null=True)), - ('department', models.CharField(blank=True, db_column='Department', max_length=255, null=True)), + ( + "certification_identifier", + models.AutoField( + db_column="Certification Identifier", + primary_key=True, + serialize=False, + ), + ), + ( + "certification_name", + models.CharField( + blank=True, + db_column="Certification Name", + max_length=255, + null=True, + ), + ), + ( + "department", + models.CharField( + blank=True, db_column="Department", max_length=255, null=True + ), + ), ], options={ - 'db_table': 'Certification Definitions', - 'ordering': ('certification_name', 'department'), + "db_table": "Certification Definitions", + "ordering": ("certification_name", "department"), }, ), migrations.CreateModel( - name='CmsRedRiverVeteransScholarship', + name="CmsRedRiverVeteransScholarship", fields=[ - ('serial', models.AutoField(primary_key=True, serialize=False)), - ('program_name', models.CharField(db_column='Program Name', max_length=255)), - ('member_name', models.CharField(blank=True, db_column='Member Name', max_length=255, null=True)), - ('discount_percent', models.DecimalField(blank=True, db_column='Discount Percent', decimal_places=0, max_digits=16, null=True)), - ('discount_code', models.CharField(blank=True, db_column='Discount Code', max_length=255, null=True)), - ('membership_code', models.CharField(blank=True, db_column='Membership Code', max_length=255, null=True)), - ('start_date', models.DateField(blank=True, db_column='Start Date', null=True)), - ('end_date', models.DateField(blank=True, db_column='End Date', null=True)), - ('program_amount', models.DecimalField(blank=True, db_column='Program Amount', decimal_places=0, max_digits=16, null=True)), - ('program_status', models.CharField(blank=True, db_column='Program Status', max_length=16, null=True)), + ("serial", models.AutoField(primary_key=True, serialize=False)), + ( + "program_name", + models.CharField(db_column="Program Name", max_length=255), + ), + ( + "member_name", + models.CharField( + blank=True, db_column="Member Name", max_length=255, null=True + ), + ), + ( + "discount_percent", + models.DecimalField( + blank=True, + db_column="Discount Percent", + decimal_places=0, + max_digits=16, + null=True, + ), + ), + ( + "discount_code", + models.CharField( + blank=True, db_column="Discount Code", max_length=255, null=True + ), + ), + ( + "membership_code", + models.CharField( + blank=True, + db_column="Membership Code", + max_length=255, + null=True, + ), + ), + ( + "start_date", + models.DateField(blank=True, db_column="Start Date", null=True), + ), + ( + "end_date", + models.DateField(blank=True, db_column="End Date", null=True), + ), + ( + "program_amount", + models.DecimalField( + blank=True, + db_column="Program Amount", + decimal_places=0, + max_digits=16, + null=True, + ), + ), + ( + "program_status", + models.CharField( + blank=True, db_column="Program Status", max_length=16, null=True + ), + ), ], options={ - 'db_table': 'CMS Red River Veterans Scholarship', + "db_table": "CMS Red River Veterans Scholarship", }, ), migrations.CreateModel( - name='InstructorOrVendor', + name="InstructorOrVendor", fields=[ - ('serial', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(db_column='Name', max_length=255)), - ('instructor_agreement_date', models.DateField(blank=True, db_column='Instructor Agreement Date', null=True)), - ('w9_date', models.DateField(blank=True, db_column='W9 date', null=True)), - ('phone', models.CharField(blank=True, max_length=255, null=True)), - ('email_address', models.CharField(blank=True, db_column='email address', max_length=255, null=True)), + ("serial", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(db_column="Name", max_length=255)), + ( + "instructor_agreement_date", + models.DateField( + blank=True, db_column="Instructor Agreement Date", null=True + ), + ), + ( + "w9_date", + models.DateField(blank=True, db_column="W9 date", null=True), + ), + ("phone", models.CharField(blank=True, max_length=255, null=True)), + ( + "email_address", + models.CharField( + blank=True, db_column="email address", max_length=255, null=True + ), + ), ], options={ - 'db_table': 'Instructors and Vendors', + "db_table": "Instructors and Vendors", }, ), migrations.CreateModel( - name='SpecialProgram', + name="SpecialProgram", fields=[ - ('program_name', models.CharField(db_column='Program Name', max_length=255, primary_key=True, serialize=False)), - ('discount_percent', models.DecimalField(blank=True, db_column='Discount Percent', decimal_places=0, max_digits=16, null=True)), - ('discount_code', models.CharField(blank=True, db_column='Discount Code', max_length=255, null=True)), - ('membership_code', models.CharField(blank=True, db_column='Membership Code', max_length=255, null=True)), - ('start_date', models.DateField(blank=True, db_column='Start Date', null=True)), - ('end_date', models.DateField(blank=True, db_column='End Date', null=True)), - ('program_amount', models.DecimalField(blank=True, db_column='Program Amount', decimal_places=0, max_digits=16, null=True)), - ('program_status', models.CharField(blank=True, db_column='Program Status', max_length=16, null=True)), + ( + "program_name", + models.CharField( + db_column="Program Name", + max_length=255, + primary_key=True, + serialize=False, + ), + ), + ( + "discount_percent", + models.DecimalField( + blank=True, + db_column="Discount Percent", + decimal_places=0, + max_digits=16, + null=True, + ), + ), + ( + "discount_code", + models.CharField( + blank=True, db_column="Discount Code", max_length=255, null=True + ), + ), + ( + "membership_code", + models.CharField( + blank=True, + db_column="Membership Code", + max_length=255, + null=True, + ), + ), + ( + "start_date", + models.DateField(blank=True, db_column="Start Date", null=True), + ), + ( + "end_date", + models.DateField(blank=True, db_column="End Date", null=True), + ), + ( + "program_amount", + models.DecimalField( + blank=True, + db_column="Program Amount", + decimal_places=0, + max_digits=16, + null=True, + ), + ), + ( + "program_status", + models.CharField( + blank=True, db_column="Program Status", max_length=16, null=True + ), + ), ], options={ - 'db_table': 'Special_Programs', + "db_table": "Special_Programs", }, ), migrations.CreateModel( - name='Waiver', + name="Waiver", fields=[ - ('number', models.AutoField(db_column='Number', primary_key=True, serialize=False)), - ('name', models.CharField(db_column='Name', max_length=255)), - ('date', models.DateField(db_column='Date')), - ('emergency_contact_name', models.CharField(blank=True, db_column='Emergency Contact Name', max_length=255, null=True)), - ('emergency_contact_number', models.CharField(blank=True, db_column='Emergency Contact Number', max_length=25, null=True)), - ('waiver_version', models.CharField(db_column='Waiver version', max_length=64)), - ('guardian_name', models.CharField(blank=True, db_column='Guardian Name', max_length=255, null=True)), - ('guardian_relation', models.CharField(blank=True, db_column='Guardian Relation', max_length=255, null=True)), - ('guardian_date', models.DateField(blank=True, db_column='Guardian Date', null=True)), + ( + "number", + models.AutoField( + db_column="Number", primary_key=True, serialize=False + ), + ), + ("name", models.CharField(db_column="Name", max_length=255)), + ("date", models.DateField(db_column="Date")), + ( + "emergency_contact_name", + models.CharField( + blank=True, + db_column="Emergency Contact Name", + max_length=255, + null=True, + ), + ), + ( + "emergency_contact_number", + models.CharField( + blank=True, + db_column="Emergency Contact Number", + max_length=25, + null=True, + ), + ), + ( + "waiver_version", + models.CharField(db_column="Waiver version", max_length=64), + ), + ( + "guardian_name", + models.CharField( + blank=True, db_column="Guardian Name", max_length=255, null=True + ), + ), + ( + "guardian_relation", + models.CharField( + blank=True, + db_column="Guardian Relation", + max_length=255, + null=True, + ), + ), + ( + "guardian_date", + models.DateField(blank=True, db_column="Guardian Date", null=True), + ), ], options={ - 'db_table': 'Waivers', + "db_table": "Waivers", }, ), migrations.CreateModel( - name='Certification', + name="Certification", fields=[ - ('number', models.AutoField(db_column='Number', primary_key=True, serialize=False)), - ('name', models.CharField(db_column='Name', max_length=255)), - ('certified_by', models.CharField(blank=True, db_column='Certified_By', max_length=255, null=True)), - ('date', models.DateField(blank=True, db_column='Date', null=True)), - ('version', models.CharField(blank=True, db_column='Version', max_length=255, null=True)), - ('shop_lead_notified', models.DateTimeField(blank=True, db_column='Shop Lead Notified', null=True)), - ('certification', models.ForeignKey(db_column='Certification', on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationdefinition')), - ('member', models.ForeignKey(blank=True, db_column='uid', db_constraint=False, null=True, on_delete=django.db.models.deletion.PROTECT, to='membershipworks.member')), - ('notes', models.CharField(blank=True, db_column='Notes', max_length=255, null=True)), + ( + "number", + models.AutoField( + db_column="Number", primary_key=True, serialize=False + ), + ), + ("name", models.CharField(db_column="Name", max_length=255)), + ( + "certified_by", + models.CharField( + blank=True, db_column="Certified_By", max_length=255, null=True + ), + ), + ("date", models.DateField(blank=True, db_column="Date", null=True)), + ( + "version", + models.CharField( + blank=True, db_column="Version", max_length=255, null=True + ), + ), + ( + "shop_lead_notified", + models.DateTimeField( + blank=True, db_column="Shop Lead Notified", null=True + ), + ), + ( + "certification", + models.ForeignKey( + db_column="Certification", + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationdefinition", + ), + ), + ( + "member", + models.ForeignKey( + blank=True, + db_column="uid", + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="membershipworks.member", + ), + ), + ( + "notes", + models.CharField( + blank=True, db_column="Notes", max_length=255, null=True + ), + ), ], options={ - 'db_table': 'Certifications', + "db_table": "Certifications", }, ), ] diff --git a/paperwork/migrations/0002_add_certification_version_model.py b/paperwork/migrations/0002_add_certification_version_model.py index da8d8d5..e057ac4 100644 --- a/paperwork/migrations/0002_add_certification_version_model.py +++ b/paperwork/migrations/0002_add_certification_version_model.py @@ -21,40 +21,69 @@ def migrate_certification_version_forward(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('membershipworks', '0001_initial'), - ('paperwork', '0001_initial'), + ("membershipworks", "0001_initial"), + ("paperwork", "0001_initial"), ] operations = [ migrations.CreateModel( - name='CertificationVersion', + name="CertificationVersion", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('version', models.CharField(blank=True, db_column='Version', max_length=255, null=True)), - ('definition', models.ForeignKey(db_column='Certification', on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationdefinition')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "version", + models.CharField( + blank=True, db_column="Version", max_length=255, null=True + ), + ), + ( + "definition", + models.ForeignKey( + db_column="Certification", + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationdefinition", + ), + ), ], ), migrations.AddConstraint( - model_name='certificationversion', - constraint=models.UniqueConstraint(fields=('definition', 'version'), name='unique_certification_version'), + model_name="certificationversion", + constraint=models.UniqueConstraint( + fields=("definition", "version"), name="unique_certification_version" + ), ), migrations.AddField( - model_name='certification', - name='certification_version', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationversion', null=True), + model_name="certification", + name="certification_version", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationversion", + null=True, + ), ), migrations.RunPython(migrate_certification_version_forward), migrations.AlterField( - model_name='certification', - name='certification_version', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='paperwork.certificationversion'), + model_name="certification", + name="certification_version", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationversion", + ), ), migrations.RemoveField( - model_name='certification', - name='certification', + model_name="certification", + name="certification", ), migrations.RemoveField( - model_name='certification', - name='version', + model_name="certification", + name="version", ), ] diff --git a/paperwork/models.py b/paperwork/models.py index 12202e2..1ebcbc4 100644 --- a/paperwork/models.py +++ b/paperwork/models.py @@ -5,39 +5,69 @@ from membershipworks.models import Member class CmsRedRiverVeteransScholarship(models.Model): serial = models.AutoField(primary_key=True) - program_name = models.CharField(db_column='Program Name', max_length=255) - member_name = models.CharField(db_column='Member Name', max_length=255, blank=True, null=True) - discount_percent = models.DecimalField(db_column='Discount Percent', max_digits=16, decimal_places=0, blank=True, null=True) - discount_code = models.CharField(db_column='Discount Code', max_length=255, blank=True, null=True) - membership_code = models.CharField(db_column='Membership Code', max_length=255, blank=True, null=True) - start_date = models.DateField(db_column='Start Date', blank=True, null=True) - end_date = models.DateField(db_column='End Date', blank=True, null=True) - program_amount = models.DecimalField(db_column='Program Amount', max_digits=16, decimal_places=0, blank=True, null=True) - program_status = models.CharField(db_column='Program Status', max_length=16, blank=True, null=True) + program_name = models.CharField(db_column="Program Name", max_length=255) + member_name = models.CharField( + db_column="Member Name", max_length=255, blank=True, null=True + ) + discount_percent = models.DecimalField( + db_column="Discount Percent", + max_digits=16, + decimal_places=0, + blank=True, + null=True, + ) + discount_code = models.CharField( + db_column="Discount Code", max_length=255, blank=True, null=True + ) + membership_code = models.CharField( + db_column="Membership Code", max_length=255, blank=True, null=True + ) + start_date = models.DateField(db_column="Start Date", blank=True, null=True) + end_date = models.DateField(db_column="End Date", blank=True, null=True) + program_amount = models.DecimalField( + db_column="Program Amount", + max_digits=16, + decimal_places=0, + blank=True, + null=True, + ) + program_status = models.CharField( + db_column="Program Status", max_length=16, blank=True, null=True + ) def __str__(self): return f"{self.program_name} {self.member_name}" class Meta: - db_table = 'CMS Red River Veterans Scholarship' + db_table = "CMS Red River Veterans Scholarship" class CertificationDefinition(models.Model): - certification_identifier = models.AutoField(db_column='Certification Identifier', primary_key=True) - certification_name = models.CharField(db_column='Certification Name', max_length=255, blank=True, null=True) - department = models.CharField(db_column='Department', max_length=255, blank=True, null=True) + certification_identifier = models.AutoField( + db_column="Certification Identifier", primary_key=True + ) + certification_name = models.CharField( + db_column="Certification Name", max_length=255, blank=True, null=True + ) + department = models.CharField( + db_column="Department", max_length=255, blank=True, null=True + ) def __str__(self): return f"{self.certification_name} <{self.department}>" class Meta: - db_table = 'Certification Definitions' - ordering = ('certification_name', 'department') + db_table = "Certification Definitions" + ordering = ("certification_name", "department") class CertificationVersion(models.Model): - definition = models.ForeignKey(CertificationDefinition, on_delete=models.PROTECT, db_column='Certification') - version = models.CharField(db_column='Version', max_length=255, blank=True, null=True) + definition = models.ForeignKey( + CertificationDefinition, on_delete=models.PROTECT, db_column="Certification" + ) + version = models.CharField( + db_column="Version", max_length=255, blank=True, null=True + ) def __str__(self): return f"{self.definition} [{self.version}]" @@ -51,67 +81,113 @@ class CertificationVersion(models.Model): class Certification(models.Model): - number = models.AutoField(db_column='Number', primary_key=True) - certification_version = models.ForeignKey(CertificationVersion, on_delete=models.PROTECT) - name = models.CharField(db_column='Name', max_length=255) - member = models.ForeignKey(Member, on_delete=models.PROTECT, to_field='uid', db_column='uid', blank=True, null=True, db_constraint=False) - certified_by = models.CharField(db_column='Certified_By', max_length=255, blank=True, null=True) - date = models.DateField(db_column='Date', blank=True, null=True) - shop_lead_notified = models.DateTimeField(db_column='Shop Lead Notified', blank=True, null=True) - notes = models.CharField(db_column='Notes', max_length=255, blank=True, null=True) + number = models.AutoField(db_column="Number", primary_key=True) + certification_version = models.ForeignKey( + CertificationVersion, on_delete=models.PROTECT + ) + name = models.CharField(db_column="Name", max_length=255) + member = models.ForeignKey( + Member, + on_delete=models.PROTECT, + to_field="uid", + db_column="uid", + blank=True, + null=True, + db_constraint=False, + ) + certified_by = models.CharField( + db_column="Certified_By", max_length=255, blank=True, null=True + ) + date = models.DateField(db_column="Date", blank=True, null=True) + shop_lead_notified = models.DateTimeField( + db_column="Shop Lead Notified", blank=True, null=True + ) + notes = models.CharField(db_column="Notes", max_length=255, blank=True, null=True) def __str__(self): return f"{self.name} - {self.certification_version}" class Meta: - db_table = 'Certifications' + db_table = "Certifications" class InstructorOrVendor(models.Model): serial = models.AutoField(primary_key=True) - name = models.CharField(db_column='Name', max_length=255) - instructor_agreement_date = models.DateField(db_column='Instructor Agreement Date', blank=True, null=True) - w9_date = models.DateField(db_column='W9 date', blank=True, null=True) + name = models.CharField(db_column="Name", max_length=255) + instructor_agreement_date = models.DateField( + db_column="Instructor Agreement Date", blank=True, null=True + ) + w9_date = models.DateField(db_column="W9 date", blank=True, null=True) phone = models.CharField(max_length=255, blank=True, null=True) - email_address = models.CharField(db_column='email address', max_length=255, blank=True, null=True) + email_address = models.CharField( + db_column="email address", max_length=255, blank=True, null=True + ) def __str__(self): return f"{self.name}" class Meta: - db_table = 'Instructors and Vendors' + db_table = "Instructors and Vendors" class SpecialProgram(models.Model): - program_name = models.CharField(db_column='Program Name', primary_key=True, max_length=255) - discount_percent = models.DecimalField(db_column='Discount Percent', max_digits=16, decimal_places=0, blank=True, null=True) - discount_code = models.CharField(db_column='Discount Code', max_length=255, blank=True, null=True) - membership_code = models.CharField(db_column='Membership Code', max_length=255, blank=True, null=True) - start_date = models.DateField(db_column='Start Date', blank=True, null=True) - end_date = models.DateField(db_column='End Date', blank=True, null=True) - program_amount = models.DecimalField(db_column='Program Amount', max_digits=16, decimal_places=0, blank=True, null=True) - program_status = models.CharField(db_column='Program Status', max_length=16, blank=True, null=True) + program_name = models.CharField( + db_column="Program Name", primary_key=True, max_length=255 + ) + discount_percent = models.DecimalField( + db_column="Discount Percent", + max_digits=16, + decimal_places=0, + blank=True, + null=True, + ) + discount_code = models.CharField( + db_column="Discount Code", max_length=255, blank=True, null=True + ) + membership_code = models.CharField( + db_column="Membership Code", max_length=255, blank=True, null=True + ) + start_date = models.DateField(db_column="Start Date", blank=True, null=True) + end_date = models.DateField(db_column="End Date", blank=True, null=True) + program_amount = models.DecimalField( + db_column="Program Amount", + max_digits=16, + decimal_places=0, + blank=True, + null=True, + ) + program_status = models.CharField( + db_column="Program Status", max_length=16, blank=True, null=True + ) def __str__(self): return self.program_name class Meta: - db_table = 'Special_Programs' + db_table = "Special_Programs" class Waiver(models.Model): - number = models.AutoField(db_column='Number', primary_key=True) - name = models.CharField(db_column='Name', max_length=255) - date = models.DateField(db_column='Date') - emergency_contact_name = models.CharField(db_column='Emergency Contact Name', max_length=255, blank=True, null=True) - emergency_contact_number = models.CharField(db_column='Emergency Contact Number', max_length=25, blank=True, null=True) - waiver_version = models.CharField(db_column='Waiver version', max_length=64) - guardian_name = models.CharField(db_column='Guardian Name', max_length=255, blank=True, null=True) - guardian_relation = models.CharField(db_column='Guardian Relation', max_length=255, blank=True, null=True) - guardian_date = models.DateField(db_column='Guardian Date', blank=True, null=True) + number = models.AutoField(db_column="Number", primary_key=True) + name = models.CharField(db_column="Name", max_length=255) + date = models.DateField(db_column="Date") + emergency_contact_name = models.CharField( + db_column="Emergency Contact Name", max_length=255, blank=True, null=True + ) + emergency_contact_number = models.CharField( + db_column="Emergency Contact Number", max_length=25, blank=True, null=True + ) + waiver_version = models.CharField(db_column="Waiver version", max_length=64) + guardian_name = models.CharField( + db_column="Guardian Name", max_length=255, blank=True, null=True + ) + guardian_relation = models.CharField( + db_column="Guardian Relation", max_length=255, blank=True, null=True + ) + guardian_date = models.DateField(db_column="Guardian Date", blank=True, null=True) def __str__(self): return f"{self.name} {self.date}" class Meta: - db_table = 'Waivers' + db_table = "Waivers" diff --git a/paperwork/routers.py b/paperwork/routers.py index 02d52d4..ec907a5 100644 --- a/paperwork/routers.py +++ b/paperwork/routers.py @@ -1,7 +1,7 @@ class PaperworkRouter: - app_label = 'paperwork' - related_app_labels = {'paperwork', 'membershipworks'} - db = 'cms' + app_label = "paperwork" + related_app_labels = {"paperwork", "membershipworks"} + db = "cms" def db_for_read(self, model, **hints): if model._meta.app_label == self.app_label: @@ -14,7 +14,9 @@ class PaperworkRouter: return None def allow_relation(self, obj1, obj2, **hints): - if obj1._meta.app_label in self.related_app_labels or \ - obj2._meta.app_label in self.related_app_labels: + if ( + obj1._meta.app_label in self.related_app_labels + or obj2._meta.app_label in self.related_app_labels + ): return True return None diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..28f4e25 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1 @@ +[tool.black] From f92a08c8511c24590a34e19230503b3f028d2384 Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Fri, 11 Feb 2022 23:52:31 -0500 Subject: [PATCH 22/24] Switch from Pipenv to PDM --- .gitignore | 1 + .pdm.toml | 5 + Pipfile | 15 -- Pipfile.lock | 115 -------------- pdm.lock | 422 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 35 ++++ 6 files changed, 463 insertions(+), 130 deletions(-) create mode 100644 .pdm.toml delete mode 100644 Pipfile delete mode 100644 Pipfile.lock create mode 100644 pdm.lock diff --git a/.gitignore b/.gitignore index 9ea61b2..3745c0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ *.sqlite3 +/__pypackages__/ diff --git a/.pdm.toml b/.pdm.toml new file mode 100644 index 0000000..0e88b87 --- /dev/null +++ b/.pdm.toml @@ -0,0 +1,5 @@ +[python] +path = "/usr/bin/python" + +[strategy] +save = "compatible" diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 5c5bbd4..0000000 --- a/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -django = "~=4.0" -mysqlclient = "~=2.1" -django-auth-ldap = "~=4.0" -django-admin-logs = "~=1.0" - -[dev-packages] - -[requires] -python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index edaa57b..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,115 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "19857e48b0277129d8fe2499742545bbb58183069d59946c495ede86bc367bea" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "asgiref": { - "hashes": [ - "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", - "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" - ], - "markers": "python_version >= '3.7'", - "version": "==3.5.0" - }, - "django": { - "hashes": [ - "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a", - "sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a" - ], - "index": "pypi", - "version": "==4.0.2" - }, - "django-admin-logs": { - "hashes": [ - "sha256:81753c20d372bc5562fe4a09090418bbb61b308388e851b19192873a472fa3d1", - "sha256:aedb5df940d32c10423d65136343bc009727df8a5a49ed0196e65241d823a890" - ], - "index": "pypi", - "version": "==1.0.2" - }, - "django-auth-ldap": { - "hashes": [ - "sha256:276f79e624ce083ce13f161387f65ff1c0efe83ef8a42f2b9830d43317b15239", - "sha256:94119c94981809124d3dc4bed974f71c7a980666896df626f556a88a5fe0b59c" - ], - "index": "pypi", - "version": "==4.0.0" - }, - "mysqlclient": { - "hashes": [ - "sha256:02c8826e6add9b20f4cb12dcf016485f7b1d6e30356a1204d05431867a1b3947", - "sha256:2c8410f54492a3d2488a6a53e2d85b7e016751a1e7d116e7aea9c763f59f5e8c", - "sha256:973235686f1b720536d417bf0a0d39b4ab3d5086b2b6ad5e6752393428c02b12", - "sha256:b62d23c11c516cedb887377c8807628c1c65d57593b57853186a6ee18b0c6a5b", - "sha256:e6279263d5a9feca3e0edbc2b2a52c057375bf301d47da2089c075ff76331d14" - ], - "index": "pypi", - "version": "==2.1.0" - }, - "pyasn1": { - "hashes": [ - "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", - "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", - "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", - "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", - "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", - "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", - "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", - "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", - "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", - "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", - "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", - "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", - "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" - ], - "version": "==0.4.8" - }, - "pyasn1-modules": { - "hashes": [ - "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", - "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", - "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", - "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", - "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", - "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", - "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", - "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", - "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", - "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", - "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", - "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", - "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" - ], - "version": "==0.2.8" - }, - "python-ldap": { - "hashes": [ - "sha256:60464c8fc25e71e0fd40449a24eae482dcd0fb7fcf823e7de627a6525b3e0d12" - ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" - }, - "sqlparse": { - "hashes": [ - "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", - "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" - ], - "markers": "python_version >= '3.5'", - "version": "==0.4.2" - } - }, - "develop": {} -} diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..6e40151 --- /dev/null +++ b/pdm.lock @@ -0,0 +1,422 @@ +[[package]] +name = "asgiref" +version = "3.5.0" +requires_python = ">=3.7" +summary = "ASGI specs, helper code, and adapters" + +[[package]] +name = "black" +version = "22.1.0" +requires_python = ">=3.6.2" +summary = "The uncompromising code formatter." +dependencies = [ + "click>=8.0.0", + "mypy-extensions>=0.4.3", + "pathspec>=0.9.0", + "platformdirs>=2", + "tomli>=1.1.0", + "typing-extensions>=3.10.0.0; python_version < \"3.10\"", +] + +[[package]] +name = "click" +version = "8.0.3" +requires_python = ">=3.6" +summary = "Composable command line interface toolkit" +dependencies = [ + "colorama; platform_system == \"Windows\"", +] + +[[package]] +name = "colorama" +version = "0.4.4" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "Cross-platform colored terminal text." + +[[package]] +name = "django" +version = "4.0.2" +requires_python = ">=3.8" +summary = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +dependencies = [ + "asgiref<4,>=3.4.1", + "sqlparse>=0.2.2", + "tzdata; sys_platform == \"win32\"", +] + +[[package]] +name = "django-admin-logs" +version = "1.0.2" +requires_python = ">=3.5" +summary = "View, delete or disable Django admin log entries." +dependencies = [ + "Django>=2.1", +] + +[[package]] +name = "django-auth-ldap" +version = "4.0.0" +requires_python = ">=3.6" +summary = "Django LDAP authentication backend." +dependencies = [ + "Django>=2.2", + "python-ldap>=3.1", +] + +[[package]] +name = "djlint" +version = "0.7.4" +requires_python = ">=3.7,<4.0" +summary = "HTML Template Linter and Formatter" +dependencies = [ + "PyYAML<7.0,>=6.0", + "click<9.0.0,>=8.0.1", + "colorama<0.5.0,>=0.4.4", + "importlib-metadata<5.0.0,>=4.10.1", + "pathspec<0.10.0,>=0.9.0", + "regex<2023.0.0,>=2022.1.18", + "tomlkit<0.9.0,>=0.8.0", + "tqdm<5.0.0,>=4.62.2", +] + +[[package]] +name = "importlib-metadata" +version = "4.11.0" +requires_python = ">=3.7" +summary = "Read metadata from Python packages" +dependencies = [ + "zipp>=0.5", +] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +summary = "Experimental type system extensions for programs checked with the mypy typechecker." + +[[package]] +name = "mysqlclient" +version = "2.1.0" +requires_python = ">=3.5" +summary = "Python interface to MySQL" + +[[package]] +name = "pathspec" +version = "0.9.0" +summary = "Utility library for gitignore style pattern matching of file paths." + +[[package]] +name = "platformdirs" +version = "2.5.0" +requires_python = ">=3.7" +summary = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." + +[[package]] +name = "pyasn1" +version = "0.4.8" +summary = "ASN.1 types and codecs" + +[[package]] +name = "pyasn1-modules" +version = "0.2.8" +summary = "A collection of ASN.1-based protocols modules." +dependencies = [ + "pyasn1<0.5.0,>=0.4.6", +] + +[[package]] +name = "python-ldap" +version = "3.4.0" +requires_python = ">=3.6" +summary = "Python modules for implementing LDAP clients" +dependencies = [ + "pyasn1-modules>=0.1.5", + "pyasn1>=0.3.7", +] + +[[package]] +name = "pyyaml" +version = "6.0" +requires_python = ">=3.6" +summary = "YAML parser and emitter for Python" + +[[package]] +name = "regex" +version = "2022.1.18" +summary = "Alternative regular expression module, to replace re." + +[[package]] +name = "sqlparse" +version = "0.4.2" +requires_python = ">=3.5" +summary = "A non-validating SQL parser." + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" + +[[package]] +name = "tomlkit" +version = "0.8.0" +requires_python = ">=3.6,<4.0" +summary = "Style preserving TOML library" + +[[package]] +name = "tqdm" +version = "4.62.3" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +summary = "Fast, Extensible Progress Meter" +dependencies = [ + "colorama; platform_system == \"Windows\"", +] + +[[package]] +name = "typing-extensions" +version = "4.0.1" +requires_python = ">=3.6" +summary = "Backported and Experimental Type Hints for Python 3.6+" + +[[package]] +name = "tzdata" +version = "2021.5" +requires_python = ">=2" +summary = "Provider of IANA time zone data" + +[[package]] +name = "zipp" +version = "3.7.0" +requires_python = ">=3.7" +summary = "Backport of pathlib-compatible object wrapper for zip files" + +[metadata] +lock_version = "3.1" +content_hash = "sha256:55cc2d62a3ec2d70115485f20fced290264220ac8d7522db79bfd76aecb42892" + +[metadata.files] +"asgiref 3.5.0" = [ + {file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"}, + {file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"}, +] +"black 22.1.0" = [ + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, +] +"click 8.0.3" = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +"colorama 0.4.4" = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +"django 4.0.2" = [ + {file = "Django-4.0.2-py3-none-any.whl", hash = "sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a"}, + {file = "Django-4.0.2.tar.gz", hash = "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a"}, +] +"django-admin-logs 1.0.2" = [ + {file = "django_admin_logs-1.0.2-py3-none-any.whl", hash = "sha256:81753c20d372bc5562fe4a09090418bbb61b308388e851b19192873a472fa3d1"}, + {file = "django-admin-logs-1.0.2.tar.gz", hash = "sha256:aedb5df940d32c10423d65136343bc009727df8a5a49ed0196e65241d823a890"}, +] +"django-auth-ldap 4.0.0" = [ + {file = "django_auth_ldap-4.0.0-py3-none-any.whl", hash = "sha256:94119c94981809124d3dc4bed974f71c7a980666896df626f556a88a5fe0b59c"}, + {file = "django-auth-ldap-4.0.0.tar.gz", hash = "sha256:276f79e624ce083ce13f161387f65ff1c0efe83ef8a42f2b9830d43317b15239"}, +] +"djlint 0.7.4" = [ + {file = "djlint-0.7.4-py3-none-any.whl", hash = "sha256:1bced1cf7b0712b00d33a8df8418aff56ccea01d21b79a94ca6e876cf763e6b5"}, + {file = "djlint-0.7.4.tar.gz", hash = "sha256:aad18147db996cb93d63493126ad29d13251f60e6e45f98ab0e9b6252875ccf0"}, +] +"importlib-metadata 4.11.0" = [ + {file = "importlib_metadata-4.11.0-py3-none-any.whl", hash = "sha256:6affcdb3aec542dd98df8211e730bba6c5f2bec8288d47bacacde898f548c9ad"}, + {file = "importlib_metadata-4.11.0.tar.gz", hash = "sha256:9e5e553bbba1843cb4a00823014b907616be46ee503d2b9ba001d214a8da218f"}, +] +"mypy-extensions 0.4.3" = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +"mysqlclient 2.1.0" = [ + {file = "mysqlclient-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:02c8826e6add9b20f4cb12dcf016485f7b1d6e30356a1204d05431867a1b3947"}, + {file = "mysqlclient-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b62d23c11c516cedb887377c8807628c1c65d57593b57853186a6ee18b0c6a5b"}, + {file = "mysqlclient-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2c8410f54492a3d2488a6a53e2d85b7e016751a1e7d116e7aea9c763f59f5e8c"}, + {file = "mysqlclient-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6279263d5a9feca3e0edbc2b2a52c057375bf301d47da2089c075ff76331d14"}, + {file = "mysqlclient-2.1.0.tar.gz", hash = "sha256:973235686f1b720536d417bf0a0d39b4ab3d5086b2b6ad5e6752393428c02b12"}, +] +"pathspec 0.9.0" = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +"platformdirs 2.5.0" = [ + {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, + {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, +] +"pyasn1 0.4.8" = [ + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] +"pyasn1-modules 0.2.8" = [ + {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, + {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, +] +"python-ldap 3.4.0" = [ + {file = "python-ldap-3.4.0.tar.gz", hash = "sha256:60464c8fc25e71e0fd40449a24eae482dcd0fb7fcf823e7de627a6525b3e0d12"}, +] +"pyyaml 6.0" = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +"regex 2022.1.18" = [ + {file = "regex-2022.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34316bf693b1d2d29c087ee7e4bb10cdfa39da5f9c50fa15b07489b4ab93a1b5"}, + {file = "regex-2022.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a0b9f6a1a15d494b35f25ed07abda03209fa76c33564c09c9e81d34f4b919d7"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99112aed4fb7cee00c7f77e8b964a9b10f69488cdff626ffd797d02e2e4484f"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a2bf98ac92f58777c0fafc772bf0493e67fcf677302e0c0a630ee517a43b949"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8618d9213a863c468a865e9d2ec50221015f7abf52221bc927152ef26c484b4c"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b52cc45e71657bc4743a5606d9023459de929b2a198d545868e11898ba1c3f59"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e12949e5071c20ec49ef00c75121ed2b076972132fc1913ddf5f76cae8d10b4"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b02e3e72665cd02afafb933453b0c9f6c59ff6e3708bd28d0d8580450e7e88af"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:abfcb0ef78df0ee9df4ea81f03beea41849340ce33a4c4bd4dbb99e23ec781b6"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6213713ac743b190ecbf3f316d6e41d099e774812d470422b3a0f137ea635832"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:61ebbcd208d78658b09e19c78920f1ad38936a0aa0f9c459c46c197d11c580a0"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b013f759cd69cb0a62de954d6d2096d648bc210034b79b1881406b07ed0a83f9"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9187500d83fd0cef4669385cbb0961e227a41c0c9bc39219044e35810793edf7"}, + {file = "regex-2022.1.18-cp310-cp310-win32.whl", hash = "sha256:94c623c331a48a5ccc7d25271399aff29729fa202c737ae3b4b28b89d2b0976d"}, + {file = "regex-2022.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:1a171eaac36a08964d023eeff740b18a415f79aeb212169080c170ec42dd5184"}, + {file = "regex-2022.1.18-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:49810f907dfe6de8da5da7d2b238d343e6add62f01a15d03e2195afc180059ed"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2f5c3f7057530afd7b739ed42eb04f1011203bc5e4663e1e1d01bb50f813e3"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85ffd6b1cb0dfb037ede50ff3bef80d9bf7fa60515d192403af6745524524f3b"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba37f11e1d020969e8a779c06b4af866ffb6b854d7229db63c5fdddfceaa917f"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e27ea1ebe4a561db75a880ac659ff439dec7f55588212e71700bb1ddd5af9"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37978254d9d00cda01acc1997513f786b6b971e57b778fbe7c20e30ae81a97f3"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54a1eb9fd38f2779e973d2f8958fd575b532fe26013405d1afb9ee2374e7ab8"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:768632fd8172ae03852e3245f11c8a425d95f65ff444ce46b3e673ae5b057b74"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:de2923886b5d3214be951bc2ce3f6b8ac0d6dfd4a0d0e2a4d2e5523d8046fdfb"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1333b3ce73269f986b1fa4d5d395643810074dc2de5b9d262eb258daf37dc98f"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:d19a34f8a3429bd536996ad53597b805c10352a8561d8382e05830df389d2b43"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d2f355a951f60f0843f2368b39970e4667517e54e86b1508e76f92b44811a8a"}, + {file = "regex-2022.1.18-cp36-cp36m-win32.whl", hash = "sha256:2245441445099411b528379dee83e56eadf449db924648e5feb9b747473f42e3"}, + {file = "regex-2022.1.18-cp36-cp36m-win_amd64.whl", hash = "sha256:25716aa70a0d153cd844fe861d4f3315a6ccafce22b39d8aadbf7fcadff2b633"}, + {file = "regex-2022.1.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e070d3aef50ac3856f2ef5ec7214798453da878bb5e5a16c16a61edf1817cc3"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22709d701e7037e64dae2a04855021b62efd64a66c3ceed99dfd684bfef09e38"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9099bf89078675c372339011ccfc9ec310310bf6c292b413c013eb90ffdcafc"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04611cc0f627fc4a50bc4a9a2e6178a974c6a6a4aa9c1cca921635d2c47b9c87"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:552a39987ac6655dad4bf6f17dd2b55c7b0c6e949d933b8846d2e312ee80005a"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e031899cb2bc92c0cf4d45389eff5b078d1936860a1be3aa8c94fa25fb46ed8"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2dacb3dae6b8cc579637a7b72f008bff50a94cde5e36e432352f4ca57b9e54c4"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e5c31d70a478b0ca22a9d2d76d520ae996214019d39ed7dd93af872c7f301e52"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb804c7d0bfbd7e3f33924ff49757de9106c44e27979e2492819c16972ec0da2"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:36b2d700a27e168fa96272b42d28c7ac3ff72030c67b32f37c05616ebd22a202"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:16f81025bb3556eccb0681d7946e2b35ff254f9f888cff7d2120e8826330315c"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:da80047524eac2acf7c04c18ac7a7da05a9136241f642dd2ed94269ef0d0a45a"}, + {file = "regex-2022.1.18-cp37-cp37m-win32.whl", hash = "sha256:6ca45359d7a21644793de0e29de497ef7f1ae7268e346c4faf87b421fea364e6"}, + {file = "regex-2022.1.18-cp37-cp37m-win_amd64.whl", hash = "sha256:38289f1690a7e27aacd049e420769b996826f3728756859420eeee21cc857118"}, + {file = "regex-2022.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6014038f52b4b2ac1fa41a58d439a8a00f015b5c0735a0cd4b09afe344c94899"}, + {file = "regex-2022.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b5d6f9aed3153487252d00a18e53f19b7f52a1651bc1d0c4b5844bc286dfa52"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d24b03daf7415f78abc2d25a208f234e2c585e5e6f92f0204d2ab7b9ab48e3"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf594cc7cc9d528338d66674c10a5b25e3cde7dd75c3e96784df8f371d77a298"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd914db437ec25bfa410f8aa0aa2f3ba87cdfc04d9919d608d02330947afaeab"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90b6840b6448203228a9d8464a7a0d99aa8fa9f027ef95fe230579abaf8a6ee1"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11772be1eb1748e0e197a40ffb82fb8fd0d6914cd147d841d9703e2bef24d288"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a602bdc8607c99eb5b391592d58c92618dcd1537fdd87df1813f03fed49957a6"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7e26eac9e52e8ce86f915fd33380f1b6896a2b51994e40bb094841e5003429b4"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:519c0b3a6fbb68afaa0febf0d28f6c4b0a1074aefc484802ecb9709faf181607"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3c7ea86b9ca83e30fa4d4cd0eaf01db3ebcc7b2726a25990966627e39577d729"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51f02ca184518702975b56affde6c573ebad4e411599005ce4468b1014b4786c"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:385ccf6d011b97768a640e9d4de25412204fbe8d6b9ae39ff115d4ff03f6fe5d"}, + {file = "regex-2022.1.18-cp38-cp38-win32.whl", hash = "sha256:1f8c0ae0a0de4e19fddaaff036f508db175f6f03db318c80bbc239a1def62d02"}, + {file = "regex-2022.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:760c54ad1b8a9b81951030a7e8e7c3ec0964c1cb9fee585a03ff53d9e531bb8e"}, + {file = "regex-2022.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93c20777a72cae8620203ac11c4010365706062aa13aaedd1a21bb07adbb9d5d"}, + {file = "regex-2022.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6aa427c55a0abec450bca10b64446331b5ca8f79b648531138f357569705bc4a"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38baee6bdb7fe1b110b6b3aaa555e6e872d322206b7245aa39572d3fc991ee4"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:752e7ddfb743344d447367baa85bccd3629c2c3940f70506eb5f01abce98ee68"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8acef4d8a4353f6678fd1035422a937c2170de58a2b29f7da045d5249e934101"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c73d2166e4b210b73d1429c4f1ca97cea9cc090e5302df2a7a0a96ce55373f1c"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24c89346734a4e4d60ecf9b27cac4c1fee3431a413f7aa00be7c4d7bbacc2c4d"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:596f5ae2eeddb79b595583c2e0285312b2783b0ec759930c272dbf02f851ff75"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ecfe51abf7f045e0b9cdde71ca9e153d11238679ef7b5da6c82093874adf3338"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1d6301f5288e9bdca65fab3de6b7de17362c5016d6bf8ee4ba4cbe833b2eda0f"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:93cce7d422a0093cfb3606beae38a8e47a25232eea0f292c878af580a9dc7605"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cf0db26a1f76aa6b3aa314a74b8facd586b7a5457d05b64f8082a62c9c49582a"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:defa0652696ff0ba48c8aff5a1fac1eef1ca6ac9c660b047fc8e7623c4eb5093"}, + {file = "regex-2022.1.18-cp39-cp39-win32.whl", hash = "sha256:6db1b52c6f2c04fafc8da17ea506608e6be7086715dab498570c3e55e4f8fbd1"}, + {file = "regex-2022.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:ebaeb93f90c0903233b11ce913a7cb8f6ee069158406e056f884854c737d2442"}, + {file = "regex-2022.1.18.tar.gz", hash = "sha256:97f32dc03a8054a4c4a5ab5d761ed4861e828b2c200febd4e46857069a483916"}, +] +"sqlparse 0.4.2" = [ + {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, + {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, +] +"tomli 2.0.1" = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +"tomlkit 0.8.0" = [ + {file = "tomlkit-0.8.0-py3-none-any.whl", hash = "sha256:b824e3466f1d475b2b5f1c392954c6cb7ea04d64354ff7300dc7c14257dc85db"}, + {file = "tomlkit-0.8.0.tar.gz", hash = "sha256:29e84a855712dfe0e88a48f6d05c21118dbafb283bb2eed614d46f80deb8e9a1"}, +] +"tqdm 4.62.3" = [ + {file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"}, + {file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"}, +] +"typing-extensions 4.0.1" = [ + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, +] +"tzdata 2021.5" = [ + {file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"}, + {file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"}, +] +"zipp 3.7.0" = [ + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, +] diff --git a/pyproject.toml b/pyproject.toml index 28f4e25..bdf0d79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1 +1,36 @@ +[project] +name = "CMS Management" +version = "0.1.0" +description = "" +authors = [ + {name = "Adam Goldsmith", email = "contact@adamgoldsmith.name"}, +] +dependencies = [ + "django~=4.0", + "mysqlclient~=2.1", + "django-auth-ldap~=4.0", + "django-admin-logs~=1.0", +] +requires-python = ">=3.9" + +[project.optional-dependencies] + [tool.black] + +[[tool.pdm.source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[tool.pdm.dev-dependencies] +lint = [ + "black~=22.1", + "djlint~=0.7", +] + +[tool.pdm.scripts] +start = "./manage.py runserver" + +[build-system] +requires = ["pdm-pep517"] +build-backend = "pdm.pep517.api" From 61fc2386e536e139a4a7a578ba642b89b75390ed Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Sat, 12 Feb 2022 00:07:16 -0500 Subject: [PATCH 23/24] Make initial migration for membershipworks app Also retroactively make it a dependency for paperwork migrations --- membershipworks/migrations/0001_initial.py | 145 ++++++++++++++++++ membershipworks/migrations/__init__.py | 0 paperwork/migrations/0001_initial.py | 4 +- .../0002_add_certification_version_model.py | 1 - 4 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 membershipworks/migrations/0001_initial.py create mode 100644 membershipworks/migrations/__init__.py diff --git a/membershipworks/migrations/0001_initial.py b/membershipworks/migrations/0001_initial.py new file mode 100644 index 0000000..d8ade02 --- /dev/null +++ b/membershipworks/migrations/0001_initial.py @@ -0,0 +1,145 @@ +# Generated by Django 4.0.2 on 2022-02-12 05:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Flag', + fields=[ + ('id', models.CharField(max_length=24, primary_key=True, serialize=False)), + ('name', models.TextField(null=True)), + ('type', models.CharField(max_length=6)), + ], + options={ + 'db_table': 'flag', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Member', + fields=[ + ('uid', models.CharField(max_length=24, primary_key=True, serialize=False)), + ('year_of_birth', models.TextField(db_column='Year of Birth', null=True)), + ('account_name', models.TextField(db_column='Account Name', null=True)), + ('first_name', models.TextField(db_column='First Name', null=True)), + ('last_name', models.TextField(db_column='Last Name', null=True)), + ('phone', models.TextField(db_column='Phone', null=True)), + ('email', models.TextField(db_column='Email', null=True)), + ('address_street', models.TextField(db_column='Address (Street)', null=True)), + ('address_city', models.TextField(db_column='Address (City)', null=True)), + ('address_state_province', models.TextField(db_column='Address (State/Province)', null=True)), + ('address_postal_code', models.TextField(db_column='Address (Postal Code)', null=True)), + ('address_country', models.TextField(db_column='Address (Country)', null=True)), + ('profile_description', models.TextField(db_column='Profile description', null=True)), + ('website', models.TextField(db_column='Website', null=True)), + ('fax', models.TextField(db_column='Fax', null=True)), + ('contact_person', models.TextField(db_column='Contact Person', null=True)), + ('password', models.TextField(db_column='Password', null=True)), + ('position_relation', models.TextField(db_column='Position/relation', null=True)), + ('parent_account_id', models.TextField(db_column='Parent Account ID', null=True)), + ('gift_membership_purchased_by', models.TextField(db_column='Gift Membership purchased by', null=True)), + ('purchased_gift_membership_for', models.TextField(db_column='Purchased Gift Membership for', null=True)), + ('closet_storage', models.TextField(db_column='Closet Storage #', null=True)), + ('storage_shelf', models.TextField(db_column='Storage Shelf #', null=True)), + ('personal_studio_space', models.TextField(db_column='Personal Studio Space #', null=True)), + ('access_permitted_shops_during_extended_hours', models.BooleanField(db_column='Access Permitted Shops During Extended Hours?')), + ('normal_access_permitted_during_covid19_limited_operations', models.BooleanField(db_column='Normal Access Permitted During COVID-19 Limited Operations')), + ('access_permitted_during_covid19_staffed_period_only', models.BooleanField(db_column='Access Permitted During COVID-19 Staffed Period Only')), + ('access_front_door_and_studio_space_during_extended_hours', models.BooleanField(db_column='Access Front Door and Studio Space During Extended Hours?')), + ('access_wood_shop', models.BooleanField(db_column='Access Wood Shop?')), + ('access_metal_shop', models.BooleanField(db_column='Access Metal Shop?')), + ('access_storage_closet', models.BooleanField(db_column='Access Storage Closet?')), + ('access_studio_space', models.BooleanField(db_column='Access Studio Space?')), + ('access_front_door', models.BooleanField(db_column='Access Front Door?')), + ('access_card_number', models.TextField(db_column='Access Card Number', null=True)), + ('access_card_facility_code', models.TextField(db_column='Access Card Facility Code', null=True)), + ('auto_billing_id', models.TextField(db_column='Auto Billing ID', null=True)), + ('billing_method', models.TextField(db_column='Billing Method', null=True)), + ('renewal_date', models.DateField(db_column='Renewal Date', null=True)), + ('join_date', models.DateField(db_column='Join Date', null=True)), + ('admin_note', models.TextField(db_column='Admin note', null=True)), + ('profile_gallery_image_url', models.TextField(db_column='Profile gallery image URL', null=True)), + ('business_card_image_url', models.TextField(db_column='Business card image URL', null=True)), + ('instagram', models.TextField(db_column='Instagram', null=True)), + ('pinterest', models.TextField(db_column='Pinterest', null=True)), + ('youtube', models.TextField(db_column='Youtube', null=True)), + ('yelp', models.TextField(db_column='Yelp', null=True)), + ('google', models.TextField(db_column='Google+', null=True)), + ('bbb', models.TextField(db_column='BBB', null=True)), + ('twitter', models.TextField(db_column='Twitter', null=True)), + ('facebook', models.TextField(db_column='Facebook', null=True)), + ('linked_in', models.TextField(db_column='LinkedIn', null=True)), + ('do_not_show_street_address_in_profile', models.TextField(db_column='Do not show street address in profile', null=True)), + ('do_not_list_in_directory', models.TextField(db_column='Do not list in directory', null=True)), + ('how_did_you_hear', models.TextField(db_column='HowDidYouHear', null=True)), + ('authorize_charge', models.TextField(db_column='authorizeCharge', null=True)), + ('policy_agreement', models.TextField(db_column='policyAgreement', null=True)), + ('waiver_form_signed_and_on_file_date', models.DateField(db_column='Waiver form signed and on file date.', null=True)), + ('membership_agreement_signed_and_on_file_date', models.DateField(db_column='Membership Agreement signed and on file date.', null=True)), + ('ip_address', models.TextField(db_column='IP Address', null=True)), + ('audit_date', models.DateField(db_column='Audit Date', null=True)), + ('agreement_version', models.TextField(db_column='Agreement Version', null=True)), + ('paperwork_status', models.TextField(db_column='Paperwork status', null=True)), + ('membership_agreement_dated', models.BooleanField(db_column='Membership agreement dated')), + ('membership_agreement_acknowledgement_page_filled_out', models.BooleanField(db_column='Membership Agreement Acknowledgement Page Filled Out')), + ('membership_agreement_signed', models.BooleanField(db_column='Membership Agreement Signed')), + ('liability_form_filled_out', models.BooleanField(db_column='Liability Form Filled Out')), + ('self_certify_essential_business', models.BooleanField(db_column='selfCertifyEssentialBusiness')), + ('accepted_covid19_policy', models.BooleanField(db_column='Accepted COVID-19 Policy')), + ], + options={ + 'db_table': 'members', + 'ordering': ('first_name', 'last_name'), + 'managed': False, + }, + ), + migrations.CreateModel( + name='MemberFlag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'memberflag', + 'managed': False, + }, + ), + migrations.CreateModel( + name='Transaction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sid', models.CharField(max_length=27, null=True)), + ('timestamp', models.DateTimeField()), + ('type', models.TextField(null=True)), + ('sum', models.DecimalField(decimal_places=4, max_digits=13, null=True)), + ('fee', models.DecimalField(decimal_places=4, max_digits=13, null=True)), + ('event_id', models.TextField(null=True)), + ('for_what', models.TextField(db_column='For', null=True)), + ('items', models.TextField(db_column='Items', null=True)), + ('discount_code', models.TextField(db_column='Discount Code', null=True)), + ('note', models.TextField(db_column='Note', null=True)), + ('name', models.TextField(db_column='Name', null=True)), + ('contact_person', models.TextField(db_column='Contact Person', null=True)), + ('full_address', models.TextField(db_column='Full Address', null=True)), + ('street', models.TextField(db_column='Street', null=True)), + ('city', models.TextField(db_column='City', null=True)), + ('state_province', models.TextField(db_column='State/Province', null=True)), + ('postal_code', models.TextField(db_column='Postal Code', null=True)), + ('country', models.TextField(db_column='Country', null=True)), + ('phone', models.TextField(db_column='Phone', null=True)), + ('email', models.TextField(db_column='Email', null=True)), + ('member', models.ForeignKey(db_column='uid', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to='membershipworks.member')), + ], + options={ + 'db_table': 'transactions', + }, + ), + ] diff --git a/membershipworks/migrations/__init__.py b/membershipworks/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/paperwork/migrations/0001_initial.py b/paperwork/migrations/0001_initial.py index 5dd5dc9..8ca1fca 100644 --- a/paperwork/migrations/0001_initial.py +++ b/paperwork/migrations/0001_initial.py @@ -8,7 +8,9 @@ class Migration(migrations.Migration): initial = True - dependencies = [] + dependencies = [ + ("membershipworks", "0001_initial"), + ] operations = [ migrations.CreateModel( diff --git a/paperwork/migrations/0002_add_certification_version_model.py b/paperwork/migrations/0002_add_certification_version_model.py index e057ac4..74c701a 100644 --- a/paperwork/migrations/0002_add_certification_version_model.py +++ b/paperwork/migrations/0002_add_certification_version_model.py @@ -21,7 +21,6 @@ def migrate_certification_version_forward(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("membershipworks", "0001_initial"), ("paperwork", "0001_initial"), ] From 1f5ffcc5d3491522ba04f158c21e773c8e6b7d9f Mon Sep 17 00:00:00 2001 From: Adam Goldsmith Date: Mon, 14 Feb 2022 11:05:35 -0500 Subject: [PATCH 24/24] [paperwork] Add admin action to send notification emails for new certifications --- paperwork/admin.py | 34 +++++- paperwork/certification_emails.py | 102 ++++++++++++++++ .../admin_certifications_email.dj.html | 22 ++++ .../department_certifications_email.dj.html | 22 ++++ .../member_certifications_email.dj.html | 22 ++++ pdm.lock | 110 +++++++++++++++++- pyproject.toml | 3 + 7 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 paperwork/certification_emails.py create mode 100644 paperwork/templates/paperwork/admin_certifications_email.dj.html create mode 100644 paperwork/templates/paperwork/department_certifications_email.dj.html create mode 100644 paperwork/templates/paperwork/member_certifications_email.dj.html diff --git a/paperwork/admin.py b/paperwork/admin.py index 6fe9f24..1849dff 100644 --- a/paperwork/admin.py +++ b/paperwork/admin.py @@ -1,4 +1,6 @@ -from django.contrib import admin +from django.core import mail +from django.contrib import admin, messages +from django.db.models.functions import Now from .models import ( CmsRedRiverVeteransScholarship, @@ -9,6 +11,7 @@ from .models import ( SpecialProgram, Waiver, ) +from .certification_emails import all_certification_emails class CertificationVersionInline(admin.TabularInline): @@ -79,6 +82,35 @@ class CertificationAdmin(admin.ModelAdmin): ("shop_lead_notified", admin.EmptyFieldListFilter), ] + actions = ["send_notifications"] + + @admin.action( + description="Notify Shop Leads and Members of selected certifications" + ) + def send_notifications(self, request, queryset): + try: + emails = list(all_certification_emails(queryset)) + print(emails) + + with mail.get_connection() as conn: + conn.send_messages(emails) + + for cert in queryset: + cert.update(shop_lead_notified=Now()) + + self.message_user( + request, + f"{len(emails)} notifications sent for {len(queryset)} certifications", + messages.SUCCESS, + ) + except Exception as e: + self.message_user( + request, + f"Failed to send notifications! {e}", + messages.ERROR, + ) + raise + @admin.register(InstructorOrVendor) class InstructorOrVendorAdmin(admin.ModelAdmin): diff --git a/paperwork/certification_emails.py b/paperwork/certification_emails.py new file mode 100644 index 0000000..e6374f5 --- /dev/null +++ b/paperwork/certification_emails.py @@ -0,0 +1,102 @@ +from itertools import groupby + +from django.core import mail +from django.core.mail.message import sanitize_address +from django.contrib.auth import get_user_model +from django.template import loader + +from markdownify import markdownify +import mdformat + +from membershipworks.models import Member + + +def make_multipart_email(subject, html_body, to): + plain_body = mdformat.text(markdownify(html_body), extensions={"tables"}) + + email = mail.EmailMultiAlternatives( + subject, + plain_body, + "Claremont MakerSpace Member Certification System ", + to, + reply_to=["Claremont MakerSpace "], + ) + + email.attach_alternative(html_body, "text/html") + return email + + +def make_department_email(department, certifications): + template = loader.get_template("paperwork/department_certifications_email.dj.html") + shop_leads = Member.objects.filter( + flags__type="label", flags__name="Shop Lead: " + department + ).values_list("first_name", "account_name", "email", named=True) + + html_body = template.render( + { + "shop_lead_names": [shop_lead.first_name for shop_lead in shop_leads], + "department": department, + "certifications": certifications, + } + ) + + return make_multipart_email( + f"{len(certifications)} new CMS Certifications issued for {department}", + html_body, + to=[ + sanitize_address((shop_lead.account_name, shop_lead.email), "ascii") + for shop_lead in shop_leads + ], + ) + + +def department_emails(ordered_queryset): + certifications_by_department = groupby( + ordered_queryset, lambda c: c.certification_version.definition.department + ) + + for department, certifications in certifications_by_department: + yield make_department_email(department, list(certifications)) + + +def make_member_email(member, certifications): + template = loader.get_template("paperwork/member_certifications_email.dj.html") + + html_body = template.render({"member": member, "certifications": certifications}) + + return make_multipart_email( + f"You have been issued {len(certifications)} new CMS Certifications", + html_body, + to=[sanitize_address((member.account_name, member.email), "ascii")], + ) + + +def member_emails(ordered_queryset): + certifications_by_member = groupby( + ordered_queryset.filter(member__isnull=False), lambda c: c.member + ) + + for member, certifications in certifications_by_member: + yield make_member_email(member, list(certifications)) + + +def admin_email(ordered_queryset): + template = loader.get_template("paperwork/admin_certifications_email.dj.html") + html_body = template.render({"certifications": ordered_queryset}) + + return make_multipart_email( + f"{len(ordered_queryset)} new CMS Certifications issued", + html_body, + # TODO: Admin emails should probably be from a group, not all staff + to=[get_user_model().filter(is_staff=True).values("email", flat=True)], + ) + + +def all_certification_emails(queryset): + ordered_queryset = queryset.select_related( + "certification_version__definition" + ).order_by("certification_version__definition__department") + + yield from department_emails(ordered_queryset) + yield from member_emails(ordered_queryset) + yield admin_email(ordered_queryset) diff --git a/paperwork/templates/paperwork/admin_certifications_email.dj.html b/paperwork/templates/paperwork/admin_certifications_email.dj.html new file mode 100644 index 0000000..5dac06d --- /dev/null +++ b/paperwork/templates/paperwork/admin_certifications_email.dj.html @@ -0,0 +1,22 @@ +

The following Claremont MakerSpace Member Certifications have been issued:

+ + + + + + + + + + + {% for certification in certifications %} + + + + + + + + + {% endfor %} +
CertificationVersionMemberDepartmentCertified ByDate
{{ certification.certification_version.definition.certification_name }}{{ certification.certification_version.version }}{{ certification.member }}{{ certification.certification_version.definition.department }}{{ certification.certified_by }}{{ certification.date }}
diff --git a/paperwork/templates/paperwork/department_certifications_email.dj.html b/paperwork/templates/paperwork/department_certifications_email.dj.html new file mode 100644 index 0000000..522d582 --- /dev/null +++ b/paperwork/templates/paperwork/department_certifications_email.dj.html @@ -0,0 +1,22 @@ +

Dear {{ shop_lead_names|join:", " }}:

+ +

The following Claremont MakerSpace Member Certifications have been issued for the {{ department }}:

+ + + + + + + + + + {% for certification in certifications %} + + + + + + + + {% endfor %} +
CertificationVersionMemberCertified ByDate
{{ certification.certification_version.definition.certification_name }}{{ certification.certification_version.version }}{{ certification.member }}{{ certification.certified_by }}{{ certification.date }}
diff --git a/paperwork/templates/paperwork/member_certifications_email.dj.html b/paperwork/templates/paperwork/member_certifications_email.dj.html new file mode 100644 index 0000000..79a090e --- /dev/null +++ b/paperwork/templates/paperwork/member_certifications_email.dj.html @@ -0,0 +1,22 @@ +

Dear {{ member.first_name }}:

+ +

You have been issued the following Claremont MakerSpace Member Certifications:

+ + + + + + + + + + {% for certification in certifications %} + + + + + + + + {% endfor %} +
CertificationVersionDepartmentCertified ByDate
{{ certification.certification_version.definition.certification_name }}{{ certification.certification_version.version }}{{ certification.certification_version.definition.department }}{{ certification.certified_by }}{{ certification.date }}
diff --git a/pdm.lock b/pdm.lock index 6e40151..cb74b6f 100644 --- a/pdm.lock +++ b/pdm.lock @@ -4,6 +4,21 @@ version = "3.5.0" requires_python = ">=3.7" summary = "ASGI specs, helper code, and adapters" +[[package]] +name = "attrs" +version = "21.4.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "Classes Without Boilerplate" + +[[package]] +name = "beautifulsoup4" +version = "4.10.0" +requires_python = ">3.0.0" +summary = "Screen-scraping library" +dependencies = [ + "soupsieve>1.2", +] + [[package]] name = "black" version = "22.1.0" @@ -88,6 +103,51 @@ dependencies = [ "zipp>=0.5", ] +[[package]] +name = "markdown-it-py" +version = "2.0.1" +requires_python = "~=3.6" +summary = "Python port of markdown-it. Markdown parsing, done right!" +dependencies = [ + "attrs<22,>=19", + "mdurl~=0.1", +] + +[[package]] +name = "markdownify" +version = "0.10.3" +summary = "Convert HTML to markdown." +dependencies = [ + "beautifulsoup4<5,>=4.9", + "six<2,>=1.15", +] + +[[package]] +name = "mdformat" +version = "0.7.13" +requires_python = ">=3.7,<4.0" +summary = "CommonMark compliant Markdown formatter" +dependencies = [ + "importlib-metadata>=3.6.0; python_version < \"3.10\"", + "markdown-it-py<3.0.0,>=1.0.0b2", + "tomli>=1.1.0", +] + +[[package]] +name = "mdformat-tables" +version = "0.4.1" +requires_python = ">=3.6.1" +summary = "An mdformat plugin for rendering tables." +dependencies = [ + "mdformat<0.8.0,>=0.7.5", +] + +[[package]] +name = "mdurl" +version = "0.1.0" +requires_python = ">=3.6" +summary = "Markdown URL utilities" + [[package]] name = "mypy-extensions" version = "0.4.3" @@ -144,6 +204,18 @@ name = "regex" version = "2022.1.18" summary = "Alternative regular expression module, to replace re." +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" + +[[package]] +name = "soupsieve" +version = "2.3.1" +requires_python = ">=3.6" +summary = "A modern CSS selector implementation for Beautiful Soup." + [[package]] name = "sqlparse" version = "0.4.2" @@ -191,13 +263,21 @@ summary = "Backport of pathlib-compatible object wrapper for zip files" [metadata] lock_version = "3.1" -content_hash = "sha256:55cc2d62a3ec2d70115485f20fced290264220ac8d7522db79bfd76aecb42892" +content_hash = "sha256:2852221dbf368756c0afb4737dcabf25dac20ed97d9f8e2c1d27676e8e42645e" [metadata.files] "asgiref 3.5.0" = [ {file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"}, {file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"}, ] +"attrs 21.4.0" = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +"beautifulsoup4 4.10.0" = [ + {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"}, + {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, +] "black 22.1.0" = [ {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, @@ -251,6 +331,26 @@ content_hash = "sha256:55cc2d62a3ec2d70115485f20fced290264220ac8d7522db79bfd76ae {file = "importlib_metadata-4.11.0-py3-none-any.whl", hash = "sha256:6affcdb3aec542dd98df8211e730bba6c5f2bec8288d47bacacde898f548c9ad"}, {file = "importlib_metadata-4.11.0.tar.gz", hash = "sha256:9e5e553bbba1843cb4a00823014b907616be46ee503d2b9ba001d214a8da218f"}, ] +"markdown-it-py 2.0.1" = [ + {file = "markdown_it_py-2.0.1-py3-none-any.whl", hash = "sha256:31974138ca8cafbcb62213f4974b29571b940e78364584729233f59b8dfdb8bd"}, + {file = "markdown-it-py-2.0.1.tar.gz", hash = "sha256:7b5c153ae1ab2cde00a33938bce68f3ad5d68fbe363f946de7d28555bed4e08a"}, +] +"markdownify 0.10.3" = [ + {file = "markdownify-0.10.3-py3-none-any.whl", hash = "sha256:edad0ad3896ec7460d05537ad804bbb3614877c6cd0df27b56dee218236d9ce2"}, + {file = "markdownify-0.10.3.tar.gz", hash = "sha256:782e310390cd5e4bde7543ceb644598c78b9824ee9f8d7ef9f9f4f8782e46974"}, +] +"mdformat 0.7.13" = [ + {file = "mdformat-0.7.13-py3-none-any.whl", hash = "sha256:accca5fb17da270b63d27ce05c86eba1e8dd2a0342e9c7bb4cff4e50040b65bb"}, + {file = "mdformat-0.7.13.tar.gz", hash = "sha256:28ede5e435ba84154e332edced33ee24fa30453d8fcbfbf7e41cd126a0851112"}, +] +"mdformat-tables 0.4.1" = [ + {file = "mdformat_tables-0.4.1-py3-none-any.whl", hash = "sha256:981f3dc7350027f78e3fd6a5fe8a16e123eec423af2d140e588d855751501019"}, + {file = "mdformat_tables-0.4.1.tar.gz", hash = "sha256:3024e88e9d29d7b8bb07fd6b59c9d5dcf14d2060122be29e30e72d27b65d7da9"}, +] +"mdurl 0.1.0" = [ + {file = "mdurl-0.1.0-py3-none-any.whl", hash = "sha256:40654d6dcb8d21501ed13c21cc0bd6fc42ff07ceb8be30029e5ae63ebc2ecfda"}, + {file = "mdurl-0.1.0.tar.gz", hash = "sha256:94873a969008ee48880fb21bad7de0349fef529f3be178969af5817239e9b990"}, +] "mypy-extensions 0.4.3" = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, @@ -392,6 +492,14 @@ content_hash = "sha256:55cc2d62a3ec2d70115485f20fced290264220ac8d7522db79bfd76ae {file = "regex-2022.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:ebaeb93f90c0903233b11ce913a7cb8f6ee069158406e056f884854c737d2442"}, {file = "regex-2022.1.18.tar.gz", hash = "sha256:97f32dc03a8054a4c4a5ab5d761ed4861e828b2c200febd4e46857069a483916"}, ] +"six 1.16.0" = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +"soupsieve 2.3.1" = [ + {file = "soupsieve-2.3.1-py3-none-any.whl", hash = "sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb"}, + {file = "soupsieve-2.3.1.tar.gz", hash = "sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"}, +] "sqlparse 0.4.2" = [ {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, diff --git a/pyproject.toml b/pyproject.toml index bdf0d79..df251e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,9 @@ dependencies = [ "mysqlclient~=2.1", "django-auth-ldap~=4.0", "django-admin-logs~=1.0", + "markdownify~=0.10", + "mdformat~=0.7", + "mdformat-tables~=0.4", ] requires-python = ">=3.9"