2023-04-25 23:23:27 -04:00
from typing import Optional
2022-02-28 16:06:10 -05:00
from datetime import datetime
2023-04-25 23:23:27 -04:00
2023-01-17 16:26:53 -05:00
import django . core . mail . message
from django . conf import settings
2022-02-03 13:45:58 -05:00
from django . db import models
2023-08-26 20:18:23 -04:00
from django . db . models import Exists , OuterRef
2023-12-19 23:43:49 -05:00
from django . utils import timezone
2022-02-03 13:45:58 -05:00
2022-02-28 16:06:10 -05:00
class BaseModel ( models . Model ) :
_csv_headers_override = { }
_date_fields = { }
class Meta :
abstract = True
@classmethod
2022-03-02 17:17:30 -05:00
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
2022-02-28 16:06:10 -05:00
2022-03-02 17:17:30 -05:00
csv_header = field . column
if field . name in cls . _csv_headers_override :
csv_header = cls . _csv_headers_override [ field . name ]
2022-02-28 16:06:10 -05:00
2022-03-02 17:17:30 -05:00
yield field . name , data [ csv_header ]
2022-02-28 16:06:10 -05:00
@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 ) :
2022-02-10 16:51:32 -05:00
id = models . CharField ( max_length = 24 , primary_key = True )
2022-02-27 18:45:06 -05:00
name = models . TextField ( null = True , blank = True )
2022-02-10 16:51:32 -05:00
type = models . CharField ( max_length = 6 )
def __str__ ( self ) :
2022-02-11 13:48:47 -05:00
return f " { self . name } ( { self . type } ) "
2022-02-10 16:51:32 -05:00
class Meta :
2022-02-11 13:48:47 -05:00
db_table = " flag "
2023-01-20 13:16:47 -05:00
ordering = ( " name " , )
2022-02-10 16:51:32 -05:00
2023-01-03 23:16:06 -05:00
class MemberQuerySet ( models . QuerySet ) :
2023-08-26 20:18:23 -04:00
# TODO: maybe rename to reflect EXISTS?
2023-01-03 23:16:06 -05:00
@staticmethod
def has_flag ( flag_type : str , flag_name : str ) :
2023-09-07 10:56:22 -04:00
return Exists (
Flag . objects . filter ( type = flag_type , name = flag_name , members = OuterRef ( " pk " ) )
)
2023-08-26 20:18:23 -04:00
# TODO: it should be fairly easy to reduce the number of EXISTS by
# merging the ORed flags
2023-01-03 23:16:06 -05:00
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 " )
)
)
2022-02-10 16:51:32 -05:00
# TODO: is this still a temporal table?
2022-02-28 16:06:10 -05:00
class Member ( BaseModel ) :
2023-01-03 23:16:06 -05:00
objects = MemberQuerySet . as_manager ( )
2022-02-10 16:51:32 -05:00
uid = models . CharField ( max_length = 24 , primary_key = True )
2022-02-27 18:45:06 -05:00
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 )
2022-02-10 16:51:32 -05:00
address_state_province = models . TextField (
2022-02-27 18:45:06 -05:00
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
2022-02-10 16:51:32 -05:00
)
2022-02-27 18:45:06 -05:00
storage_shelf = models . TextField ( db_column = " Storage Shelf # " , null = True , blank = True )
2022-02-11 13:48:47 -05:00
personal_studio_space = models . TextField (
2022-02-27 18:45:06 -05:00
db_column = " Personal Studio Space # " , null = True , blank = True
2022-02-11 13:48:47 -05:00
)
2022-02-10 16:51:32 -05:00
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? " )
2022-02-27 18:45:06 -05:00
access_card_number = models . TextField (
db_column = " Access Card Number " , null = True , blank = True
)
2022-02-10 16:51:32 -05:00
access_card_facility_code = models . TextField (
2022-02-27 18:45:06 -05:00
db_column = " Access Card Facility Code " , null = True , blank = True
)
auto_billing_id = models . TextField (
db_column = " Auto Billing ID " , null = True , blank = True
2022-02-10 16:51:32 -05:00
)
2022-02-27 18:45:06 -05:00
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 )
2022-02-10 16:51:32 -05:00
profile_gallery_image_url = models . TextField (
2022-02-27 18:45:06 -05:00
db_column = " Profile gallery image URL " , null = True , blank = True
2022-02-10 16:51:32 -05:00
)
business_card_image_url = models . TextField (
2022-02-27 18:45:06 -05:00
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 )
2022-02-10 16:51:32 -05:00
do_not_show_street_address_in_profile = models . TextField (
2022-02-27 18:45:06 -05:00
db_column = " Do not show street address in profile " , null = True , blank = True
2022-02-10 16:51:32 -05:00
)
do_not_list_in_directory = models . TextField (
2022-02-27 18:45:06 -05:00
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
2022-02-10 16:51:32 -05:00
)
waiver_form_signed_and_on_file_date = models . DateField (
2022-02-27 18:45:06 -05:00
db_column = " Waiver form signed and on file date. " , null = True , blank = True
2022-02-10 16:51:32 -05:00
)
membership_agreement_signed_and_on_file_date = models . DateField (
2022-02-27 18:45:06 -05:00
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
2022-02-10 16:51:32 -05:00
)
2022-02-11 13:48:47 -05:00
membership_agreement_dated = models . BooleanField (
db_column = " Membership agreement dated "
)
2022-02-10 16:51:32 -05:00
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 "
)
2022-02-11 13:48:47 -05:00
liability_form_filled_out = models . BooleanField (
db_column = " Liability Form Filled Out "
)
flags = models . ManyToManyField ( Flag , through = " MemberFlag " , related_name = " members " )
2022-02-03 13:45:58 -05:00
2022-02-28 16:06:10 -05:00
_csv_headers_override = {
2022-03-02 17:17:30 -05:00
" 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. " ,
2022-02-28 16:06:10 -05:00
}
_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 " ,
}
2022-02-03 13:45:58 -05:00
def __str__ ( self ) :
return f " { self . account_name } "
class Meta :
2022-02-11 13:48:47 -05:00
db_table = " members "
ordering = ( " first_name " , " last_name " )
2023-03-31 23:54:16 -04:00
indexes = [
2023-12-20 00:54:31 -05:00
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 " ) ,
2023-03-31 23:54:16 -04:00
]
2022-02-10 16:51:32 -05:00
2023-04-25 23:23:27 -04:00
@classmethod
def from_user ( cls , user ) - > Optional [ " Member " ] :
if hasattr ( user , " ldap_user " ) :
return cls . objects . get ( uid = user . ldap_user . attrs [ " employeeNumber " ] [ 0 ] )
2023-02-02 22:33:13 -05:00
def sanitized_mailbox ( self , name_ext : str = " " , use_volunteer = False ) - > str :
2023-02-02 21:35:27 -05:00
if use_volunteer and self . volunteer_email :
email = self . volunteer_email
2023-02-02 22:33:13 -05:00
elif self . email :
2023-02-02 21:35:27 -05:00
email = self . email
2023-02-02 22:33:13 -05:00
else :
raise Exception ( f " No Email Address for user: { self . uid } " )
if not self . account_name :
return email
2023-01-17 16:26:53 -05:00
return django . core . mail . message . sanitize_address (
2023-02-02 21:35:27 -05:00
( self . account_name + name_ext , email ) , settings . DEFAULT_CHARSET
2023-01-17 16:26:53 -05:00
)
2022-02-10 16:51:32 -05:00
2022-02-28 16:06:10 -05:00
class MemberFlag ( BaseModel ) :
2022-02-10 16:51:32 -05:00
member = models . ForeignKey ( Member , on_delete = models . PROTECT , db_column = " uid " )
flag = models . ForeignKey ( Flag , on_delete = models . PROTECT )
def __str__ ( self ) :
2022-02-11 13:48:47 -05:00
return f " { self . member } - { self . flag } "
2022-02-10 16:51:32 -05:00
class Meta :
2022-02-11 13:48:47 -05:00
db_table = " memberflag "
2022-02-10 16:51:32 -05:00
constraints = [
2022-02-11 13:48:47 -05:00
models . UniqueConstraint (
2022-02-28 17:09:48 -05:00
fields = [ " member " , " flag " ] , name = " unique_member_flag "
2022-02-11 13:48:47 -05:00
)
2022-02-10 16:51:32 -05:00
]
2023-12-19 23:42:46 -05:00
2023-12-19 23:43:49 -05:00
class Transaction ( BaseModel ) :
2022-03-03 14:03:40 -05:00
sid = models . CharField ( max_length = 256 , null = True , blank = True )
2023-12-19 23:42:46 -05:00
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 )
2023-12-19 23:43:49 -05:00
@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 = {
2022-03-02 17:17:30 -05:00
" member_id " : " uid " ,
" timestamp " : " _dp " ,
" type " : " Transaction Type " ,
" for_what " : " Event/Form Name " ,
2023-12-19 23:43:49 -05:00
}
2023-12-19 23:42:46 -05:00
def __str__ ( self ) :
return f " { self . type } [ { self . member if self . member else self . name } ] { self . timestamp } "
class Meta :
db_table = " transactions "