328 lines
13 KiB
Python
328 lines
13 KiB
Python
from typing import Optional
|
|
from datetime import datetime
|
|
|
|
import django.core.mail.message
|
|
from django.conf import settings
|
|
from django.db import models
|
|
from django.db.models import Exists, OuterRef
|
|
from django.utils import timezone
|
|
|
|
|
|
class BaseModel(models.Model):
|
|
_csv_headers_override = {}
|
|
_date_fields = {}
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
@classmethod
|
|
def _remap_headers(cls, data):
|
|
for field in cls._meta.get_fields():
|
|
# TODO: more robust filtering of fields that don't have a column
|
|
if field.auto_created or field.many_to_many or not field.concrete:
|
|
continue
|
|
|
|
field_name, csv_header = field.get_attname_column()
|
|
|
|
if field_name in cls._csv_headers_override:
|
|
csv_header = cls._csv_headers_override[field.name]
|
|
|
|
yield field_name, data[csv_header]
|
|
|
|
@classmethod
|
|
def from_csv_dict(cls, data):
|
|
data = data.copy()
|
|
|
|
# parse date fields to datetime objects
|
|
for field, fmt in cls._date_fields.items():
|
|
if data[field]:
|
|
data[field] = datetime.strptime(str(data[field]), fmt)
|
|
else:
|
|
# convert empty string to None to make NULL in SQL
|
|
data[field] = None
|
|
|
|
return cls(**dict(cls._remap_headers(data)))
|
|
|
|
|
|
class Flag(BaseModel):
|
|
id = models.CharField(max_length=24, primary_key=True)
|
|
name = models.TextField(null=True, blank=True)
|
|
type = models.CharField(max_length=6)
|
|
|
|
def __str__(self):
|
|
return f"{self.name} ({self.type})"
|
|
|
|
class Meta:
|
|
db_table = "flag"
|
|
ordering = ("name",)
|
|
|
|
|
|
class MemberQuerySet(models.QuerySet):
|
|
# TODO: maybe rename to reflect EXISTS?
|
|
@staticmethod
|
|
def has_flag(flag_type: str, flag_name: str):
|
|
return Exists(
|
|
Flag.objects.filter(type=flag_type, name=flag_name, members=OuterRef("pk"))
|
|
)
|
|
|
|
# TODO: it should be fairly easy to reduce the number of EXISTS by
|
|
# merging the ORed flags
|
|
def with_is_active(self):
|
|
return self.annotate(
|
|
is_active=(
|
|
self.has_flag("folder", "Members")
|
|
| self.has_flag("folder", "CMS Staff")
|
|
)
|
|
& ~(
|
|
self.has_flag("label", "Account On Hold")
|
|
| self.has_flag("level", "CMS Membership on hold")
|
|
| self.has_flag("folder", "Former Members")
|
|
)
|
|
)
|
|
|
|
|
|
# TODO: is this still a temporal table?
|
|
class Member(BaseModel):
|
|
objects = MemberQuerySet.as_manager()
|
|
|
|
uid = models.CharField(max_length=24, primary_key=True)
|
|
year_of_birth = models.TextField(db_column="Year of Birth", null=True, blank=True)
|
|
account_name = models.TextField(db_column="Account Name", null=True, blank=True)
|
|
first_name = models.TextField(db_column="First Name", null=True, blank=True)
|
|
last_name = models.TextField(db_column="Last Name", null=True, blank=True)
|
|
phone = models.TextField(db_column="Phone", null=True, blank=True)
|
|
email = models.TextField(db_column="Email", null=True, blank=True)
|
|
volunteer_email = models.TextField(
|
|
db_column="Volunteer Email", null=True, blank=True
|
|
)
|
|
address_street = models.TextField(
|
|
db_column="Address (Street)", null=True, blank=True
|
|
)
|
|
address_city = models.TextField(db_column="Address (City)", null=True, blank=True)
|
|
address_state_province = models.TextField(
|
|
db_column="Address (State/Province)", null=True, blank=True
|
|
)
|
|
address_postal_code = models.TextField(
|
|
db_column="Address (Postal Code)", null=True, blank=True
|
|
)
|
|
address_country = models.TextField(
|
|
db_column="Address (Country)", null=True, blank=True
|
|
)
|
|
profile_description = models.TextField(
|
|
db_column="Profile description", null=True, blank=True
|
|
)
|
|
website = models.TextField(db_column="Website", null=True, blank=True)
|
|
fax = models.TextField(db_column="Fax", null=True, blank=True)
|
|
contact_person = models.TextField(db_column="Contact Person", null=True, blank=True)
|
|
password = models.TextField(db_column="Password", null=True, blank=True)
|
|
position_relation = models.TextField(
|
|
db_column="Position/relation", null=True, blank=True
|
|
)
|
|
parent_account_id = models.TextField(
|
|
db_column="Parent Account ID", null=True, blank=True
|
|
)
|
|
closet_storage = models.TextField(
|
|
db_column="Closet Storage #", null=True, blank=True
|
|
)
|
|
storage_shelf = models.TextField(db_column="Storage Shelf #", null=True, blank=True)
|
|
personal_studio_space = models.TextField(
|
|
db_column="Personal Studio Space #", null=True, blank=True
|
|
)
|
|
access_permitted_shops_during_extended_hours = models.BooleanField(
|
|
db_column="Access Permitted Shops During Extended Hours?"
|
|
)
|
|
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, blank=True
|
|
)
|
|
access_card_facility_code = models.TextField(
|
|
db_column="Access Card Facility Code", null=True, blank=True
|
|
)
|
|
auto_billing_id = models.TextField(
|
|
db_column="Auto Billing ID", null=True, blank=True
|
|
)
|
|
billing_method = models.TextField(db_column="Billing Method", null=True, blank=True)
|
|
renewal_date = models.DateField(db_column="Renewal Date", null=True, blank=True)
|
|
join_date = models.DateField(db_column="Join Date", null=True, blank=True)
|
|
admin_note = models.TextField(db_column="Admin note", null=True, blank=True)
|
|
profile_gallery_image_url = models.TextField(
|
|
db_column="Profile gallery image URL", null=True, blank=True
|
|
)
|
|
business_card_image_url = models.TextField(
|
|
db_column="Business card image URL", null=True, blank=True
|
|
)
|
|
instagram = models.TextField(db_column="Instagram", null=True, blank=True)
|
|
pinterest = models.TextField(db_column="Pinterest", null=True, blank=True)
|
|
youtube = models.TextField(db_column="Youtube", null=True, blank=True)
|
|
yelp = models.TextField(db_column="Yelp", null=True, blank=True)
|
|
google = models.TextField(db_column="Google+", null=True, blank=True)
|
|
bbb = models.TextField(db_column="BBB", null=True, blank=True)
|
|
twitter = models.TextField(db_column="Twitter", null=True, blank=True)
|
|
facebook = models.TextField(db_column="Facebook", null=True, blank=True)
|
|
linked_in = models.TextField(db_column="LinkedIn", null=True, blank=True)
|
|
do_not_show_street_address_in_profile = models.TextField(
|
|
db_column="Do not show street address in profile", null=True, blank=True
|
|
)
|
|
do_not_list_in_directory = models.TextField(
|
|
db_column="Do not list in directory", null=True, blank=True
|
|
)
|
|
how_did_you_hear = models.TextField(
|
|
db_column="HowDidYouHear", null=True, blank=True
|
|
)
|
|
authorize_charge = models.TextField(
|
|
db_column="authorizeCharge", null=True, blank=True
|
|
)
|
|
policy_agreement = models.TextField(
|
|
db_column="policyAgreement", null=True, blank=True
|
|
)
|
|
waiver_form_signed_and_on_file_date = models.DateField(
|
|
db_column="Waiver form signed and on file date.", null=True, blank=True
|
|
)
|
|
membership_agreement_signed_and_on_file_date = models.DateField(
|
|
db_column="Membership Agreement signed and on file date.", null=True, blank=True
|
|
)
|
|
ip_address = models.TextField(db_column="IP Address", null=True, blank=True)
|
|
audit_date = models.DateField(db_column="Audit Date", null=True, blank=True)
|
|
agreement_version = models.TextField(
|
|
db_column="Agreement Version", null=True, blank=True
|
|
)
|
|
paperwork_status = models.TextField(
|
|
db_column="Paperwork status", null=True, blank=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"
|
|
)
|
|
flags = models.ManyToManyField(Flag, through="MemberFlag", related_name="members")
|
|
|
|
_csv_headers_override = {
|
|
"uid": "Account ID",
|
|
"how_did_you_hear": "Please tell us how you heard about the Claremont MakerSpace and what tools or shops you are most excited to start using:",
|
|
"authorize_charge": "Yes - I authorize TwinState MakerSpaces, Inc. to charge my credit card for the membership and other options that I have selected.",
|
|
"policy_agreement": "I have read the Claremont MakerSpace Membership Agreement & Policies, and agree to all terms stated therein.",
|
|
}
|
|
|
|
_date_fields = {
|
|
"Join Date": "%b %d, %Y",
|
|
"Renewal Date": "%b %d, %Y",
|
|
"Audit Date": "%m/%d/%Y",
|
|
"Membership Agreement signed and on file date.": "%m/%d/%Y",
|
|
"Waiver form signed and on file date.": "%m/%d/%Y",
|
|
}
|
|
|
|
def __str__(self):
|
|
return f"{self.account_name}"
|
|
|
|
class Meta:
|
|
db_table = "members"
|
|
ordering = ("first_name", "last_name")
|
|
indexes = [
|
|
models.Index(fields=["account_name"], name="account_name_idx"),
|
|
models.Index(fields=["first_name"], name="first_name_idx"),
|
|
models.Index(fields=["last_name"], name="last_name_idx"),
|
|
]
|
|
|
|
@classmethod
|
|
def from_user(cls, user) -> Optional["Member"]:
|
|
if hasattr(user, "ldap_user"):
|
|
return cls.objects.get(uid=user.ldap_user.attrs["employeeNumber"][0])
|
|
|
|
def sanitized_mailbox(self, name_ext: str = "", use_volunteer=False) -> str:
|
|
if use_volunteer and self.volunteer_email:
|
|
email = self.volunteer_email
|
|
elif self.email:
|
|
email = self.email
|
|
else:
|
|
raise Exception(f"No Email Address for user: {self.uid}")
|
|
|
|
if not self.account_name:
|
|
return email
|
|
|
|
return django.core.mail.message.sanitize_address(
|
|
(self.account_name + name_ext, email), settings.DEFAULT_CHARSET
|
|
)
|
|
|
|
|
|
class MemberFlag(BaseModel):
|
|
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:
|
|
db_table = "memberflag"
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=["member", "flag"], name="unique_member_flag"
|
|
)
|
|
]
|
|
|
|
|
|
class Transaction(BaseModel):
|
|
sid = models.CharField(max_length=256, null=True, blank=True)
|
|
member = models.ForeignKey(
|
|
Member,
|
|
on_delete=models.PROTECT,
|
|
db_column="uid",
|
|
related_name="transactions",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
timestamp = models.DateTimeField()
|
|
type = models.TextField(null=True, blank=True)
|
|
sum = models.DecimalField(max_digits=13, decimal_places=4, null=True, blank=True)
|
|
fee = models.DecimalField(max_digits=13, decimal_places=4, null=True, blank=True)
|
|
event_id = models.TextField(null=True, blank=True)
|
|
for_what = models.TextField(db_column="For", null=True, blank=True)
|
|
items = models.TextField(db_column="Items", null=True, blank=True)
|
|
discount_code = models.TextField(db_column="Discount Code", null=True, blank=True)
|
|
note = models.TextField(db_column="Note", null=True, blank=True)
|
|
name = models.TextField(db_column="Name", null=True, blank=True)
|
|
contact_person = models.TextField(db_column="Contact Person", null=True, blank=True)
|
|
full_address = models.TextField(db_column="Full Address", null=True, blank=True)
|
|
street = models.TextField(db_column="Street", null=True, blank=True)
|
|
city = models.TextField(db_column="City", null=True, blank=True)
|
|
state_province = models.TextField(db_column="State/Province", null=True, blank=True)
|
|
postal_code = models.TextField(db_column="Postal Code", null=True, blank=True)
|
|
country = models.TextField(db_column="Country", null=True, blank=True)
|
|
phone = models.TextField(db_column="Phone", null=True, blank=True)
|
|
email = models.TextField(db_column="Email", null=True, blank=True)
|
|
|
|
@classmethod
|
|
def from_csv_dict(cls, data):
|
|
txn = data.copy()
|
|
# can't use '%s' format string, have to use the special function
|
|
txn["_dp"] = datetime.fromtimestamp(
|
|
txn["_dp"], tz=timezone.get_current_timezone()
|
|
)
|
|
return super().from_csv_dict(txn)
|
|
|
|
_csv_headers_override = {
|
|
"member_id": "uid",
|
|
"timestamp": "_dp",
|
|
"type": "Transaction Type",
|
|
"for_what": "Event/Form Name",
|
|
}
|
|
|
|
def __str__(self):
|
|
return f"{self.type} [{self.member if self.member else self.name}] {self.timestamp}"
|
|
|
|
class Meta:
|
|
db_table = "transactions"
|