Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # booking/models.py
- from django.db import models
- from django.core.exceptions import ValidationError
- from people.models import Customer, UserProfile
- from venue.models import Venue, Area, Table, Booth, Package, Timeslot
- from corecrm.common import random_ref_id
- from .choices import DURATION_CHOICES
- class CustomerPackage(models.Model):
- reference = models.CharField(max_length=50, blank=True)
- package_date = models.DateField(auto_now_add=True)
- class Meta:
- ordering = ("-package_date",)
- def __unicode(self):
- return self.reference
- def save(self):
- super(CustomerPackage, self).save()
- if self.reference == '':
- self.reference = random_ref_id(self.id)
- self.save()
- def _get_customer(self):
- customer=None
- fbs=self.functionbooking_set.all()
- rbs=self.restaurantbooking_set.all()
- nbs=self.nightclubbooking_set.all()
- if any(fbs):
- customer=fbs[0].customer
- elif any(rbs):
- customer=rbs[0].customer
- elif any(nbs):
- customer=nbs[0].customer
- return customer
- customer = property(_get_customer)
- def _get_bookings(self):
- # Amalgamate the bookings of different types into one booking list
- bookings = []
- for fb in self.functionbooking_set.all():
- bookings.append(fb)
- for rb in self.restaurantbooking_set.all():
- bookings.append(rb)
- for nb in self.nightclubbooking_set.all():
- bookings.append(nb)
- if any(bookings):
- bookings = sorted(bookings, key=lambda bk: bk.booking_start)
- return bookings
- bookings = property(_get_bookings)
- def get_bookings_by_date_range(self, start_date, end_date):
- # Amalgamate the bookings of different types into one booking list with date filter
- bookings = []
- for fb in self.functionbooking_set.filter():
- bookings.append(fb)
- for rb in self.restaurantbooking_set.all():
- bookings.append(rb)
- for nb in self.nightclubbooking_set.all():
- bookings.append(nb)
- if any(bookings):
- bookings = sorted(bookings, key=lambda bk: bk.booking_start)
- return bookings
- def _get_bookings_for_api(self):
- bookings = []
- for fb in self.functionbooking_set.all():
- bk = {
- 'id': fb.id,
- 'type': fb.booking_type,
- 'customer': fb.customer.__unicode__(),
- 'venue': fb.venue.name,
- 'booking_start': fb.booking_start,
- 'pax': fb.pax,
- 'notes': fb.notes,
- 'area': fb.area.name
- }
- bookings.append(bk)
- for rb in self.restaurantbooking_set.all():
- bk = {
- 'id': rb.id,
- 'type': rb.booking_type,
- 'customer': rb.customer.__unicode__(),
- 'venue': rb.venue.name,
- 'booking_start': rb.booking_start,
- 'pax': rb.pax,
- 'notes': rb.notes,
- 'table': rb.table.number
- }
- bookings.append(bk)
- for nb in self.nightclubbooking_set.all():
- bk = {
- 'id': nb.id,
- 'type': nb.booking_type,
- 'customer': nb.customer.__unicode__(),
- 'venue': nb.venue.name,
- 'booking_start': nb.booking_start,
- 'pax': nb.pax,
- 'notes': nb.notes,
- 'booth': nb.booth.name
- }
- bookings.append(bk)
- if any(bookings):
- bookings = sorted(bookings, key=lambda bk: bk['booking_start'])
- return bookings
- api_bookings = property(_get_bookings_for_api)
- class AbstractBaseBooking(models.Model):
- customer = models.ForeignKey(Customer)
- booking_agent = models.ForeignKey(UserProfile)
- venue = models.ForeignKey(Venue)
- duration = models.CharField(max_length=10, choices=DURATION_CHOICES, default='1')
- booked_on = models.DateField(auto_now_add=True)
- booking_start = models.DateTimeField()
- pax = models.IntegerField(verbose_name='Number Of People')
- notes = models.TextField(blank=True, default='')
- customer_package = models.ForeignKey(CustomerPackage)
- guests = models.ManyToManyField(Customer, related_name='%(class)s_guests')
- def _calculate_total_duration(self):
- return int(float(self.duration) * 4)
- total_duration = property(_calculate_total_duration)
- def __unicode__(self):
- return "%s, %s, %s" % (self.customer.full_name, self.booking_start,
- self.venue.name)
- class Meta:
- abstract = True
- ordering = ('booked_on',)
- class FunctionType(models.Model):
- function_type = models.CharField(max_length=32)
- def __unicode__(self):
- return self.function_type
- class FunctionBooking(AbstractBaseBooking):
- area = models.ForeignKey(Area, help_text='You can select multiple values by holding down either ctrl (pc) or command (mac)')
- function_type = models.ForeignKey(FunctionType)
- booking_type = 'function'
- def _get_area_description(self):
- return "%s - %s" % (self.area.name, self.area.description)
- area_detail = property(_get_area_description)
- class RestaurantBooking(AbstractBaseBooking):
- table = models.ForeignKey(Table)
- booking_type = 'restaurant'
- def _get_area_description(self):
- return "%s - %s" % (self.table.area_layout.area.name,
- self.table.area_layout.area.description)
- area_detail = property(_get_area_description)
- class NightclubBooking(AbstractBaseBooking):
- booth = models.ForeignKey(Booth, null=True, blank=True)
- timeslot = models.ForeignKey(Timeslot, null=True, blank=True)
- package = models.ManyToManyField(Package)
- booking_type = 'nightclub'
- def _get_area_description(self):
- return "%s - %s" % (self.booth.area_layout.area.name,
- self.booth.area_layout.area.description)
- area_detail = property(_get_area_description)
- # people/models.py
- from django.contrib.gis.db import models
- from django.db.models import Q
- from django.conf import settings
- from django.contrib.auth.models import AbstractUser, Group
- from django.contrib.gis.geos import Point
- from django.core.exceptions import ValidationError, MultipleObjectsReturned
- from django.utils import timezone
- import csv
- from dateutil import parser
- import re
- try:
- import simplejson as json
- except ImportError:
- import json
- from audit_log.models.fields import LastUserField
- from audit_log.models.managers import AuditLog
- from unipath import Path
- from south.modelsinspector import add_introspection_rules
- add_introspection_rules([], ["^audit_log\.models\.fields\.LastUserField"])
- from tag.models import Tag
- MALE = 'M'
- FEMALE = 'F'
- GENDER_CHOICES = (
- (MALE, 'Male'),
- (FEMALE, 'Female')
- )
- def validate_format_postcode(postcode):
- # Permitted letters depend upon their position in the postcode.
- alpha1 = "[abcdefghijklmnoprstuwyz]"; # Character 1
- alpha2 = "[abcdefghklmnopqrstuvwxy]"; # Character 2
- alpha3 = "[abcdefghjkpmnrstuvwxy]"; # Character 3
- alpha4 = "[abehmnprvwxy]"; # Character 4
- alpha5 = "[abdefghjlnpqrstuwxyz]"; # Character 5
- BFPOa5 = "[abdefghjlnpqrst]{1}"; # BFPO character 5
- BFPOa6 = "[abdefghjlnpqrstuwzyz]{1}"; # BFPO character 6
- pcexp = []
- # Expression for BF1 type postcodes
- pcexp.append('(?P<part1>bf1)(?P<part2>[0-9]{1}%s%s)' % (BFPOa5, BFPOa6))
- # Expression for postcodes: AN NAA, ANN NAA, AAN NAA, and AANN NAA with a space
- pcexp.append('(?P<part1>%s{1}%s?\d{1,2})(?P<part2>\d{1}%s{2})' % (alpha1, alpha2, alpha5))
- # Expression for postcodes: ANA NAA
- pcexp.append('(?P<part2>%s{1}[0-9]{1}%s{1})(?P<part2>[0-9]{1}%s{2})' % (alpha1, alpha3, alpha5))
- # Expression for postcodes: AANA NAA
- pcexp.append('(?P<part1>%s{1}%s{1}[0-9]{1}%s)(?P<part2>[0-9]{1}%s{2})' % (alpha1, alpha2, alpha4, alpha5))
- # Exception for the special postcode GIR 0AA
- pcexp.append('(?P<part1>gir)(?P<part2>0aa)')
- # Standard BFPO numbers
- pcexp.append('(?P<part1>bfpo)(?P<part2>[0-9]{1,4})')
- # c/o BFPO numbers
- pcexp.append('(?P<part1>bfpo)(?P<part2>c\/o[0-9]{1,3})')
- # Overseas Territories
- pcexp.append('(?P<part1>[a-z]{4})(?P<part2>1zz)')
- # Anquilla
- pcexp.append('/^ai-2640$/')
- # Load up the string to check, converting into lowercase
- postcode = postcode.lower().replace(' ', '')
- # Assume we are not going to find a valid postcode
- valid = False
- # Check the string against the six types of postcodes
- for regexp in pcexp:
- try:
- m = re.search(regexp, postcode)
- if m:
- valid = True
- break
- except:
- pass
- # Return with the reformatted valid postcode in uppercase if the postcode was
- # valid
- if valid:
- return "%s %s" % (m.group('part1').upper(), m.group('part2').upper())
- else:
- return False
- def get_customer_from_csv_row(row, csv_file):
- new_customer = None
- if 'email' in csv_file.csv_fields.keys():
- check_email = row[csv_file.csv_fields['email']]
- test_first_name = None
- test_last_name = None
- if 'first_name' in csv_file.csv_fields.keys():
- test_first_name = row[csv_file.csv_fields['first_name']]
- if 'last_name' in csv_file.csv_fields.keys():
- test_last_name = row[csv_file.csv_fields['last_name']]
- try:
- new_customer, exists = (Customer.objects.get_or_create(
- email=check_email))
- # check the first and last names to determine if we
- # should use this model instance or not
- if (test_first_name is not None and
- test_last_name is not None):
- if not (new_customer.last_name in [test_last_name, ''] and
- new_customer.first_name in [test_first_name, '']):
- # The names passed in don't match those
- # currently in the database
- new_customer = Customer()
- except MultipleObjectsReturned:
- check_customers = Customer.objects.filter(email=check_email)
- for check_customer in check_customers:
- if (test_first_name is not None and
- test_last_name is not None):
- if (check_customer.last_name == test_last_name and
- check_customer.first_name == test_first_name):
- # The names passed in don't match those
- # currently in the database
- new_customer = check_customer
- break
- if new_customer is None:
- new_customer = Customer()
- else:
- new_customer = Customer()
- new_customer.is_csv = True
- return new_customer
- class UserProfile(AbstractUser):
- address1 = models.CharField(max_length=255, default='')
- address2 = models.CharField(max_length=255, blank=True)
- middle_name = models.CharField(max_length=255, blank=True,
- verbose_name="Middle Name(s)")
- city = models.CharField(max_length=255, default='')
- region = models.CharField(max_length=255, blank=True)
- country = models.CharField(max_length=255, default='')
- postcode = models.CharField(max_length=10, default='')
- dob = models.DateField(null=True, blank=True)
- gender = models.CharField(max_length=1, choices=GENDER_CHOICES, default='')
- telephone = models.CharField(max_length=20, default='')
- mobile = models.CharField(max_length=20, blank=True)
- def __unicode__(self):
- return self.full_name
- def save(self, *args, **kwargs):
- if self.username == '':
- self.username = self.email
- super(UserProfile, self).save(*args, **kwargs)
- def _get_full_name(self):
- if self.middle_name != '':
- return "%s %s %s" % (self.first_name, self.middle_name,
- self.last_name)
- else:
- return "%s %s" % (self.first_name, self.last_name)
- full_name = property(_get_full_name)
- def _get_model_fields(self):
- return self._meta.fields
- model_fields = property(_get_model_fields)
- UserProfile._meta.get_field_by_name('username')[0]._max_length = 75
- class CompanyType(models.Model):
- name = models.CharField(max_length=255)
- def __unicode__(self):
- return self.name
- class Customer(models.Model):
- first_name = models.CharField(max_length=255, blank=True, db_index=True)
- middle_name = models.CharField(max_length=255, blank=True,
- verbose_name="Middle Name(s)")
- last_name = models.CharField(max_length=255, blank=True, db_index=True)
- email = models.CharField(max_length=255, blank=True, db_index=True)
- date_joined = models.DateTimeField(null=True, blank=True)
- address1 = models.CharField(max_length=255, blank=True)
- address2 = models.CharField(max_length=255, blank=True)
- address3 = models.CharField(max_length=255, blank=True)
- address4 = models.CharField(max_length=255, blank=True)
- address5 = models.CharField(max_length=255, blank=True)
- city = models.CharField(max_length=255, blank=True, db_index=True)
- region = models.CharField(max_length=255, blank=True)
- postcode = models.CharField(max_length=255, blank=True)
- country = models.CharField(max_length=255, blank=True)
- dob = models.DateField(blank=True, null=True)
- gender = models.CharField(max_length=1, choices=GENDER_CHOICES, blank=True)
- telephone = models.CharField(max_length=20, blank=True)
- mobile = models.CharField(max_length=20, blank=True)
- is_company = models.BooleanField(default=False)
- company_type = models.ForeignKey(CompanyType, blank=True, null=True)
- latitude = models.DecimalField(max_digits=12, decimal_places=9, null=True,
- blank=True)
- longitude = models.DecimalField(max_digits=12, decimal_places=9, null=True,
- blank=True)
- audit_log = AuditLog()
- is_csv = False
- def __init__(self, *args, **kwargs):
- super(Customer, self).__init__(*args, **kwargs)
- self.tags = None
- if self.id > 0:
- self.tags = CustomerToTag.objects.filter(customer=self)
- def __unicode__(self):
- id_parts = []
- if self.first_name is not '':
- id_parts.append(self.first_name)
- if self.middle_name is not '':
- id_parts.append(self.middle_name)
- if self.last_name is not '':
- id_parts.append(self.last_name)
- if self.email is not '':
- id_parts.append("<%s>" % self.email)
- if len(id_parts) > 0:
- return ' '.join(id_parts)
- else:
- return 'No identifying information'
- def save(self, *args, **kwargs):
- self.clean()
- if self.postcode != '':
- try:
- pc = Postcode.objects.get(postcode=self.postcode)
- self.latitude = pc.latitude
- self.longitude = pc.longitude
- except Postcode.DoesNotExist:
- pass
- if self.date_joined is not None:
- if timezone.is_naive(self.date_joined):
- aware_date = timezone.make_aware(self.date_joined, timezone.get_current_timezone())
- self.date_joined = aware_date
- super(Customer, self).save(*args, **kwargs)
- def clean(self):
- if self.postcode != '':
- formatted_postcode = validate_format_postcode(self.postcode)
- if not formatted_postcode:
- if self.is_csv:
- self.postcode = ''
- else:
- raise ValidationError('The postcode is not valid')
- else:
- self.postcode = formatted_postcode
- return super(Customer, self).clean()
- def _get_full_name(self):
- name_parts = []
- if self.first_name != '':
- name_parts.append(self.first_name.strip())
- if self.middle_name != '':
- name_parts.append(self.middle_name.strip())
- if self.last_name != '':
- name_parts.append(self.last_name.strip())
- if len(name_parts) > 0:
- return ' '.join(name_parts)
- else:
- return 'No name information for this customer'
- full_name = property(_get_full_name)
- def _get_model_fields(self):
- return self._meta.fields
- model_fields = property(_get_model_fields)
- def add_items_from_csv_row(self, row, csv_file, tag_fields, date_fields):
- for item in row:
- for field_name, column in csv_file.csv_fields.items():
- '''
- For some reason some of the CSV exports have "\N" as
- a field value. Don't include those fields, or the
- fields which are empty, in the count
- '''
- if row[column].replace('\N', '').strip() is not '':
- if field_name not in tag_fields.keys():
- if field_name in date_fields:
- if row[column] is not '':
- try:
- parsed_date = parser.parse(row[column],
- dayfirst=True)
- # parser creates a naive datetime object so
- # we need to make it aware (of timezones)
- date_tzaware = timezone.make_aware(
- parsed_date,
- timezone.get_current_timezone())
- setattr(self, field_name, date_tzaware)
- except ValueError:
- # some of the date fields are
- # empty on some rows
- pass
- else:
- setattr(self, field_name, row[column])
- if field_name == 'first_name':
- # Add the gender if we can work it out
- gender = GenderName.objects.filter(
- name=row[column]).exclude(gender='')
- if len(gender) == 1:
- self.gender = gender[0].gender
- else:
- '''
- because of the way Python handles variables (they're not
- variables! See http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables
- and
- http://stackoverflow.com/a/986145/347187)
- we can update the tag_fields variable here without the
- need to return it
- '''
- tag_fields[field_name] = row[column]
- self.save()
- def get_location(self):
- if self.longitude is not None and self.latitude is not None:
- return Point(self.longitude, self.latitude)
- return None
- class Meta:
- ordering = ['last_name',]
- def filter_for_customers(filters):
- # We can filter by email, first name, last name, telephone or mobile number
- filter_fields = ('email', 'first_name', 'last_name', 'telephone', 'mobile')
- kwargs = {}
- for ff in filter_fields:
- if ff in filters.keys():
- filter_detail = "%s__icontains" % ff
- kwargs[filter_detail] = filters[ff]
- if len(kwargs) > 0:
- matching_customers = Customer.objects.filter(**kwargs)
- if matching_customers.count() > 0:
- return matching_customers
- return False
- class CustomerToTag(models.Model):
- '''
- We're using a bespoke lookup table here rather than putting a tags
- ManyToManyField in the Customer model as we need to hold some extra info
- about each tag that we can't store using that method
- '''
- SUBSCRIBED = 'S'
- UNSUBSCRIBED = 'U'
- TAG_STATUS_CHOICES = (
- (SUBSCRIBED, 'Subscribed'),
- (UNSUBSCRIBED, 'Unsubscribed')
- )
- customer = models.ForeignKey(Customer)
- tag = models.ForeignKey(Tag)
- date_created = models.DateField()
- tag_created_by = models.ForeignKey(UserProfile)
- tag_status = models.CharField(max_length=1, choices=TAG_STATUS_CHOICES)
- audit_log = AuditLog()
- def __unicode__(self):
- return "%s - %s" % (self.tag.category.name, self.tag.name)
- class Meta:
- unique_together = (('customer', 'tag'),)
- def get_customers_by_tag_name(tag, include_unsubscribed = False):
- '''
- Function to return a list of customers who have been assigned a specific
- tag, optionally allowing unsubscribed customers to be included
- '''
- if tag != '':
- kwargs = {'customertotag__tag__name': tag}
- if not include_unsubscribed:
- kwargs['customertotag__tag_status'] = 'S'
- customers = Customer.objects.filter(**kwargs)
- if customers.count() > 0:
- return customers
- return None
- def get_customers_by_tag(tag, include_unsubscribed = False):
- '''
- Function to return a list of customers who have been assigned a specific
- tag, optionally allowing unsubscribed customers to be included
- '''
- if tag > 0:
- kwargs = {'customertotag__tag': tag}
- if not include_unsubscribed:
- kwargs['customertotag__tag_status'] = 'S'
- customers = Customer.objects.filter(**kwargs)
- if customers.count() > 0:
- return customers
- return None
- FIELD_LIST = (
- ('first_name', 'First Name'),
- ('middle_name', 'Middle Name(s)'),
- ('last_name', 'Surname'),
- ('email', 'Email Address'),
- ('telephone', 'Telephone Number'),
- ('mobile', 'Mobile Number'),
- ('address1', 'Address Line 1'),
- ('address2', 'Address Line 2'),
- ('address3', 'Address Line 3'),
- ('address4', 'Address Line 4'),
- ('address5', 'Address Line 5'),
- ('city', 'Town / City'),
- ('region', 'Region'),
- ('postcode', 'Postcode'),
- ('dob', 'Date of Birth'),
- ('date_joined', 'Date Captured'),
- ('further_education', 'Further Education'),
- ('venue', 'Venue'),
- ('referral_source', 'Referral Source'),
- ('event', 'Event'),
- )
- TAG_FIELDS = {
- 'further_education': 'Further Education',
- 'venue': 'Venue',
- 'referral_source': 'Referral Source',
- 'event': 'Event'
- }
- class CustomerCsvFile(models.Model):
- source = models.CharField(max_length=100, blank=True)
- csv_file = models.FileField(upload_to='csvimport/customer/', max_length=255)
- uploaded_by = models.ForeignKey(UserProfile)
- date_uploaded = models.DateField(auto_now_add=True)
- date_imported = models.DateField(blank=True, null=True)
- field_order = models.TextField(blank=True)
- has_headings = models.BooleanField(default=True, verbose_name='First line is headings')
- reimport_filename = models.CharField(max_length=255, blank=True)
- parent = models.ForeignKey('self', blank=True, null=True)
- def __unicode__(self):
- return self.filename
- def _get_filename(self):
- return Path(self.csv_file.path).name
- filename = property(_get_filename)
- def _get_first_line(self):
- if Path(self.csv_file.path).exists():
- with open(self.csv_file.path, 'rb') as f:
- r = csv.reader(f)
- if self.has_headings:
- # skip the first line if it is headings
- r.next()
- first_line = r.next()
- f.close()
- return first_line
- else:
- return False
- first_line = property(_get_first_line)
- def _file_exists(self):
- return Path(self.csv_file.path).exists()
- file_exists = property(_file_exists)
- def get_lines(self, num_lines):
- if Path(self.csv_file.path).exists():
- #csv_file = self.csv_file_object
- rows = []
- r = csv.reader(self.csv_file_object)
- if self.has_headings:
- # skip the first line if it is headings
- r.next()
- # We're going to work under the assumption that the first empty row
- # indicates the end of the file and the loop should therefore end
- for i in range(num_lines):
- try:
- row = r.next()
- except StopIteration:
- break
- if not any(row):
- break
- rows.append(row)
- self.csv_file_object.close()
- return rows
- return False
- def _get_file(self):
- if Path(self.csv_file.path).exists():
- f = open(self.csv_file.path, 'rb')
- return f
- return False
- csv_file_object = property(_get_file)
- def _get_best_row(self):
- '''
- Grab the row which has the most columns completed to allow for the most
- comprehensive initial data assignation
- '''
- r = csv.reader(self.csv_file_object)
- if self.has_headings:
- # skip the first line if it is headings
- r.next()
- current_counter = 0
- biggest_row = []
- for row in r:
- counter = 0
- # Check that the line isn't empty
- if any(row):
- '''
- For some reason some of the CSV exports have "\N" as a field
- value. Don't count those fields, or the fields which are empty,
- in the count
- '''
- for value in row:
- if value.replace('\N', '').strip() != '':
- counter += 1
- if (counter > current_counter):
- current_counter = counter
- biggest_row = row
- return biggest_row
- best_row = property(_get_best_row)
- def _get_row_count(self):
- r = csv.reader(self.csv_file_object)
- if self.has_headings:
- # skip the first line if it is headings
- r.next()
- counter = 0
- for row in r:
- if any(row):
- counter += 1
- self.csv_file_object.seek(0)
- return counter
- row_count = property(_get_row_count)
- def _get_csv_fields(self):
- if self.field_order != '':
- field_list = json.loads(self.field_order)
- field_dict = {}
- for counter, item in enumerate(field_list):
- if (item['field_name'] != 'unknown' and
- item['field_name'].replace('\N', '').strip() is not ''):
- field_dict[item['field_name']] = counter
- return field_dict
- return {}
- csv_fields = property(_get_csv_fields)
- def _get_parent_field_order(self):
- if self.parent is not None:
- return self.parent.field_order
- else:
- return False
- parent_field_order = property(_get_parent_field_order)
- class GenderName(models.Model):
- name = models.CharField(max_length=100, unique=True)
- gender = models.CharField(max_length=1, blank=True, db_index=True)
- latitude = models.DecimalField(max_digits=12, decimal_places=9, null=True,
- blank=True)
- longitude = models.DecimalField(max_digits=12, decimal_places=9, null=True,
- blank=True)
- def __unicode__(self):
- gender = (self.gender if self.gender in [MALE, FEMALE] else 'Not Set')
- return "%s (%s)" % (self.name, gender)
- class Postcode(models.Model):
- postcode = models.CharField(max_length=8)
- latitude = models.DecimalField(max_digits=15, decimal_places=13)
- longitude = models.DecimalField(max_digits=15, decimal_places=13)
- def __unicode__(self):
- return "%s (%s, %s)" % (self.postcode, self.longitude, self.latitude)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement