class User(models.Model): name = models.CharField(max_length=50) sleep_time = models.TimeField(blank=False, default=datetime.now()) wake_time = models.TimeField(blank=False, default=datetime.now()) time_zone = TimeZoneField(default='US/Pacific') phone_regex = RegexValidator( regex=r'^\+?1?\d{9,15}$', message= "Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed." ) phone_number = models.CharField(validators=[phone_regex], max_length=17, blank=False) country = CountryField() # Additional fields not visible to users task_id = models.CharField(max_length=50, blank=True, editable=False) created = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name + ' - ' + self.phone_number def schedule_reminder(self): # Calculate the correct time to send this sms-alert sms_time = arrow.get(self.wake_time, self.time_zone.zone) reminder_time = sms_time.replace(minutes=+settings.REMINDER_TIME) # Schedule the Celery task from .tasks import send_sms_reminder result = send_sms_reminder.apply_async((self.pk, ), eta=reminder_time) return result.id
class Appointment(models.Model): name = models.CharField(max_length=150) phone_number = models.CharField(max_length=9) time = models.DateTimeField() time_zone = TimeZoneField(default="Europe/Warsaw") # additional fields: task_id = models.CharField(max_length=150, blank=True, editable=False) created = models.DateTimeField(auto_now_add=True) def __str__(self): return "Appointment number:{} - {} ".format(self.pk, self.name) def get_absolute_url(self): return reverse('view_appointment', args=[str(self.id)]) def clean(self): """Checks that appointments are not scheduled in the past""" appointment_time = arrow.get(self.time, self.time_zone.zone) if appointment_time < arrow.now('Europe/Warsaw'): raise ValidationError( 'You cannot schedule an appointment for the past. ' 'Please check your time and time_zone') def schedule_reminder(self): """Schedules a Celery task to send a reminder about this appointment""" #calculate time to send the reminder appointment_time = arrow.get(self.time, self.time_zone.zone) reminder_time = appointment_time.replace( minutes=-settings.REMINDER_TIME) # schedule the celery task from .tasks import send_sms_reminder result = send_sms_reminder.apply_async((self.pk, ), eta=reminder_time) return result.id def save(self, *args, **kwargs): """Custom save method which also schedules a reminder""" # Check if we have scheduled a reminder for this appointment before if self.task_id: # Revoke that task in case its time has changed celery_app.control.revoke(self.task_id) # Save appointment, which populates self.pk,which is used in schedule_reminder super(Appointment, self).save(*args, **kwargs) # Schedule a new reminder task for this appointment self.task_id = self.schedule_reminder() # Save our appointment again, with the new task_id super(Appointment, self).save(*args, **kwargs)
class Venue(models.Model): """A location that serves alcohol""" TAP_LIST_PROVIDERS = ( ('untappd', 'Untappd'), ('digitalpour', 'DigitalPour'), ('taphunter', 'TapHunter'), ('manual', 'Chalkboard/Whiteboard'), ('', 'Unknown'), ('test', 'TEST LOCAL PROVIDER'), ('stemandstein', 'The Stem & Stein\'s HTML'), ('taplist.io', 'taplist.io'), ('beermenus', 'BeerMenus'), ) # NOTE if this ever grows beyond HSV, we'll have to revisit uniqueness name = models.CharField(max_length=50, unique=True) address = models.CharField(max_length=50, blank=True) city = models.CharField(max_length=50, blank=True) state = models.CharField(max_length=25, blank=True) postal_code = models.CharField(max_length=10, blank=True) country = CountryField(blank=True) time_zone = TimeZoneField(default=settings.DEFAULT_VENUE_TIME_ZONE) website = models.URLField(blank=True) facebook_page = models.URLField(blank=True) twitter_handle = models.CharField(blank=True, max_length=25) instagram_handle = models.CharField(blank=True, max_length=25) tap_list_provider = models.CharField( 'What service the venue uses for digital tap lists', blank=True, max_length=30, choices=TAP_LIST_PROVIDERS, ) untappd_url = models.URLField(blank=True, null=True, unique=True) email = models.EmailField(blank=True) phone_number = models.CharField(max_length=50, blank=True) logo_url = models.URLField(blank=True) slug = models.SlugField() on_downtown_craft_beer_trail = models.BooleanField(default=False) # -90 to +90 latitude = models.DecimalField( max_digits=10, decimal_places=8, blank=True, null=True, ) # -180 to 180 longitude = models.DecimalField( max_digits=11, decimal_places=8, blank=True, null=True, ) twitter_short_location_description = models.CharField( 'Short location description for this specific location for use on ' 'Twitter', max_length=25, blank=True, ) def __str__(self): return self.name
class Contact(NameMixin, PolymorphicModel): is_anonymous = False is_all_seeing = False default_tax_group_getter = None created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on')) identifier = InternalIdentifierField(unique=True, null=True, blank=True) is_active = models.BooleanField(default=True, db_index=True, verbose_name=_('active')) # TODO: parent contact? default_shipping_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('shipping address'), on_delete=models.PROTECT ) default_billing_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('billing address'), on_delete=models.PROTECT ) default_shipping_method = models.ForeignKey( "ShippingMethod", verbose_name=_('default shipping method'), blank=True, null=True, on_delete=models.SET_NULL ) default_payment_method = models.ForeignKey( "PaymentMethod", verbose_name=_('default payment method'), blank=True, null=True, on_delete=models.SET_NULL ) language = LanguageField(verbose_name=_('language'), blank=True) marketing_permission = models.BooleanField(default=True, verbose_name=_('marketing permission')) phone = models.CharField(max_length=64, blank=True, verbose_name=_('phone')) www = models.URLField(max_length=128, blank=True, verbose_name=_('web address')) timezone = TimeZoneField(blank=True, null=True, verbose_name=_('time zone')) prefix = models.CharField(verbose_name=_('name prefix'), max_length=64, blank=True) name = models.CharField(max_length=256, verbose_name=_('name')) suffix = models.CharField(verbose_name=_('name suffix'), max_length=64, blank=True) name_ext = models.CharField(max_length=256, blank=True, verbose_name=_('name extension')) email = models.EmailField(max_length=256, blank=True, verbose_name=_('email')) tax_group = models.ForeignKey( "CustomerTaxGroup", blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('tax group') ) def __str__(self): return self.full_name class Meta: verbose_name = _('contact') verbose_name_plural = _('contacts') def __init__(self, *args, **kwargs): if self.default_tax_group_getter: kwargs.setdefault("tax_group", self.default_tax_group_getter()) super(Contact, self).__init__(*args, **kwargs)
class Website(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL) name = models.CharField(max_length=255) url = models.URLField() timezone = TimeZoneField() def __unicode__(self): return self.name def view_count_today(self): today_midnight = timezone.now().astimezone(self.timezone).replace( hour=0, minute=0, second=0) return len( PageView.objects.filter(session__website=self, view_timestamp__gt=today_midnight))
class TimezoneMixin(models.Model): timezone = TimeZoneField(blank=True, null=True) class Meta: abstract = True @property def timezone_name(self): if type(self.timezone) is str: return self.timezone return self.timezone.zone if self.timezone else '' def get_timezone_utc_relative(self): return get_timezone_utc_relative( self.timezone) if self.timezone else ''
class Venue(models.Model): """A location that serves alcohol""" TAP_LIST_PROVIDERS = ( ('untappd', 'Untappd'), ('digitalpour', 'DigitalPour'), ('taphunter', 'TapHunter'), ('nook_html', 'The Nook\'s static HTML'), ('manual', 'Chalkboard/Whiteboard'), ('', 'Unknown'), ('test', 'TEST LOCAL PROVIDER'), ('stemandstein', 'The Stem & Stein\'s HTML'), ('taplist.io', 'taplist.io'), ) # NOTE if this ever grows beyond HSV, we'll have to revisit uniqueness name = models.CharField(max_length=50, unique=True) address = models.CharField(max_length=50, blank=True) city = models.CharField(max_length=50, blank=True) state = models.CharField(max_length=25, blank=True) postal_code = models.CharField(max_length=10, blank=True) country = CountryField(blank=True) time_zone = TimeZoneField(default=settings.DEFAULT_VENUE_TIME_ZONE) website = models.URLField(blank=True) facebook_page = models.URLField(blank=True) twitter_handle = models.CharField(blank=True, max_length=25) instagram_handle = models.CharField(blank=True, max_length=25) tap_list_provider = models.CharField( 'What service the venue uses for digital tap lists', blank=True, max_length=30, choices=TAP_LIST_PROVIDERS, ) untappd_url = models.URLField(blank=True, null=True, unique=True) email = models.EmailField(blank=True) phone_number = models.CharField(max_length=50, blank=True) logo_url = models.URLField(blank=True) slug = models.SlugField() def __str__(self): return self.name
class Membership(models.Model): ROLES = Choices( # 'member' roles (2, 'member', _('Board Member')), (21, 'director', _('Executive Director')), (20, 'ceo', _('CEO')), (1, 'chair', _('Board Chair')), # 'guest' roles (6, 'staff', _('Staff')), (4, 'guest', _('Guest')), (5, 'vendor', _('Vendor')), (7, 'consultant', _('Consultant')), (3, 'assistant', _('Executive Assistant')), ) STATUS = Choices( (True, 'active', _('Active')), (False, 'inactive', _('Inactive')), ) INV_NOT_SENT, INV_SENT, INV_INVITED = range(3) INV_CHOICES = ((INV_NOT_SENT, 'NOT_SENT'), (INV_SENT, 'SENT'), (INV_INVITED, 'INVITED')) user = models.ForeignKey(User, on_delete=models.CASCADE) assistant = models.ForeignKey('self', related_name='bosses', blank=True, null=True, on_delete=models.SET_NULL) # Member Details account = models.ForeignKey(Account, related_name='memberships') committees = models.ManyToManyField(Committee, verbose_name=_('committees'), related_name='memberships', blank=True) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=30, blank=True) phone_number = models.CharField(_('phone number'), max_length=255, blank=True) secondary_phone = models.CharField(_('secondary phone'), max_length=255, blank=True) role = models.PositiveSmallIntegerField(_('role'), choices=ROLES, default=ROLES.member) # Admin can name them anything they wish? # Internally, they will be categorized as Guests/Members but the Display title will be different. custom_role_name = models.CharField(_('custom role name'), max_length=50, blank=True) is_active = models.BooleanField(_('active'), choices=STATUS, default=True) is_admin = models.BooleanField(_('admin'), default=False) date_joined_board = models.DateField(_('date joined board'), default=timezone.now) term_start = models.DateField(_('starts'), blank=True, null=True) term_expires = models.DateField(_('expires'), blank=True, null=True) timezone = TimeZoneField() # Work details employer = models.CharField(_('employer'), max_length=255, blank=True) job_title = models.CharField(_('job title'), max_length=255, blank=True) work_email = models.CharField(_('work email'), max_length=255, blank=True) work_number = models.CharField(_('work number'), max_length=255, blank=True) work_address = models.CharField(_('address'), max_length=255, blank=True) work_secondary_address = models.CharField(_('secondary address'), max_length=255, blank=True) work_city = models.CharField(_('city'), max_length=50, blank=True) work_state = models.CharField(_('state'), max_length=50, blank=True) work_zip = models.CharField(_('zip'), max_length=50, blank=True) work_country = models.CharField(_('country'), max_length=50, blank=True) # Bio graphy intro = models.CharField(_('intro'), max_length=250, blank=True) bio = models.TextField(_('bio'), blank=True) # Personal details title = models.CharField(_('title'), max_length=225, blank=True) description = models.TextField(_('description'), blank=True) address = models.CharField(_('address'), max_length=255, blank=True) secondary_address = models.CharField(_('secondary address'), max_length=255, blank=True) city = models.CharField(_('city'), max_length=50, blank=True) state = models.CharField(_('state'), max_length=50, blank=True) zip = models.CharField(_('zip'), max_length=50, blank=True) country = models.CharField(_('country'), max_length=50, blank=True) birth_date = models.CharField(_('birth date'), max_length=100, blank=True) # Social Details affiliation = models.CharField(_('affiliations'), max_length=255, blank=True) social_media_link = models.CharField(_('social media links'), max_length=255, blank=True) # Avatar avatar = models.ImageField(storage=avatar_storage, upload_to=avatar_upload_to, blank=True) crops = CropField('avatar', upload_to=crops_upload_to) position = models.CharField(_('position'), max_length=250, blank=True) # Notes notes = models.TextField(_('notes'), blank=True) # Other last_login = models.DateTimeField(_('last login'), null=True, editable=False) last_modified = models.DateTimeField(_('last modified date'), editable=False, auto_now=True) invitation_status = models.PositiveSmallIntegerField( _('invitation status'), choices=INV_CHOICES, default=INV_NOT_SENT) @property def is_invited(self): return self.invitation_status == self.INV_INVITED @property def invitation_sent(self): return self.invitation_status != self.INV_NOT_SENT def deactivate(self): self.is_active = False for boss in self.bosses.all(): boss.assistant = None boss.save() if self.assistant: assistant = self.assistant if assistant.bosses.count() == 1: assistant.is_active = False assistant.save() # Drop association self.assistant = None self.save() class Meta: unique_together = ('user', 'account') ordering = ('first_name', 'last_name') def __unicode__(self): return self.get_full_name() def get_full_name(self): """ Returns the first_name plus the last_name, with a space in between. """ if self.first_name and self.last_name: full_name = u'{} {}'.format(self.first_name, self.last_name) return full_name.strip() return self.user.email.split('@')[0] def get_short_name(self): return self.first_name def get_role_name(self): if self.custom_role_name is None or self.custom_role_name == '': return self.get_role_display() else: return self.custom_role_name def avatar_url(self, geometry=''): if self.avatar: try: if geometry: if self.crops: return get_thumbnail(self.crops.rect, geometry, crop='center', quality=100).url if 'x' in geometry: return get_thumbnail(self.avatar, geometry, crop='center', quality=100).url else: return get_thumbnail(self.avatar, geometry, quality=100).url else: return self.avatar.url except: pass return settings.DEFAULT_AVATAR_URL def list_avatar_url(self, geometry='180x120'): if self.avatar: return get_thumbnail(self.avatar, geometry, crop='center', quality=100).url return settings.DEFAULT_LIST_AVATAR_URL @property def is_staff(self): return self.role == Membership.ROLES.staff @property def is_guest(self): return self.role in (Membership.ROLES.assistant, Membership.ROLES.guest, Membership.ROLES.vendor, Membership.ROLES.staff, Membership.ROLES.consultant) @property def can_have_assistant(self): return self.role in (Membership.ROLES.chair, Membership.ROLES.member) def is_committee_chairman(self, committee=None): """Checks if member is committee chair or assistant of chair.""" if self.role == Membership.ROLES.assistant: ids = self.get_bosses().values_list('id', flat=True) else: ids = [self.id] if committee is not None: return committee.chairman.filter(id__in=ids).exists() else: return Committee.objects.filter(chairman__id__in=ids).exists() def get_bosses(self): return self.bosses.filter(is_active=True) def get_absolute_url(self): return reverse('profiles:detail', kwargs={'pk': self.pk}) @property def get_affiliation(self): return ', '.join(self.affiliation.split(',')) @property def get_phone(self): return ', '.join(self.phone_number.split(',')) @property def get_phones(self): phones = filter(lambda x: x is not None and len(x) > 6, [self.phone_number, self.secondary_phone]) return phones
class Contact(PolymorphicShuupModel): is_anonymous = False is_all_seeing = False default_tax_group_getter = None default_contact_group_identifier = None default_contact_group_name = None created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_("created on")) modified_on = models.DateTimeField(auto_now=True, editable=False, db_index=True, null=True, verbose_name=_("modified on")) identifier = InternalIdentifierField(unique=True, null=True, blank=True) is_active = models.BooleanField( default=True, db_index=True, verbose_name=_("active"), help_text=_("Enable this if the contact is an active customer."), ) shops = models.ManyToManyField( "shuup.Shop", blank=True, verbose_name=_("shops"), help_text=_("Inform which shops have access to this contact."), ) registration_shop = models.ForeignKey( on_delete=models.CASCADE, to="Shop", related_name="registrations", verbose_name=_("registration shop"), null=True, ) # TODO: parent contact? default_shipping_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_("shipping address"), on_delete=models.PROTECT, ) default_billing_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_("billing address"), on_delete=models.PROTECT, ) default_shipping_method = models.ForeignKey( "ShippingMethod", verbose_name=_("default shipping method"), blank=True, null=True, on_delete=models.SET_NULL) default_payment_method = models.ForeignKey( "PaymentMethod", verbose_name=_("default payment method"), blank=True, null=True, on_delete=models.SET_NULL) _language = LanguageField( verbose_name=_("language"), blank=True, help_text= _("The primary language to be used in all communications with the contact." ), ) marketing_permission = models.BooleanField( default=False, verbose_name=_("marketing permission"), help_text= _("Enable this if the contact can receive marketing and promotional materials." ), ) phone = models.CharField( max_length=64, blank=True, verbose_name=_("phone"), help_text=_("The primary phone number of the contact.")) www = models.URLField( max_length=128, blank=True, verbose_name=_("web address"), help_text=_("The web address of the contact, if any."), ) timezone = TimeZoneField( blank=True, null=True, verbose_name=_("time zone"), help_text=_( "The timezone in which the contact resides. " "This can be used to target the delivery of promotional materials at a particular time." ), ) prefix = models.CharField( verbose_name=_("name prefix"), max_length=64, blank=True, help_text=_( "The name prefix of the contact. For example, Mr, Mrs, Dr, etc."), ) name = models.CharField(max_length=256, verbose_name=_("name"), help_text=_("The contact name")) suffix = models.CharField( verbose_name=_("name suffix"), max_length=64, blank=True, help_text=_( "The name suffix of the contact. For example, Sr, Jr, etc."), ) name_ext = models.CharField(max_length=256, blank=True, verbose_name=_("name extension")) email = models.EmailField( max_length=256, blank=True, verbose_name=_("email"), help_text= _("The email that will receive order confirmations and promotional materials (if permitted)." ), ) tax_group = models.ForeignKey( "CustomerTaxGroup", blank=True, null=True, on_delete=models.PROTECT, verbose_name=_("tax group"), help_text= _("Select the contact tax group to use for this contact. " "Tax groups can be used to customize the tax rules the that apply to any of this contact's " "orders. Tax groups are defined in `Customer Tax Groups` and can be applied to tax rules " "in `Tax Rules`."), ) merchant_notes = models.TextField( blank=True, verbose_name=_("merchant notes"), help_text= _("Enter any private notes for this customer that are only accessible in Shuup admin." ), ) account_manager = models.ForeignKey(on_delete=models.CASCADE, to="PersonContact", blank=True, null=True, verbose_name=_("account manager")) options = PolymorphicJSONField(blank=True, null=True, verbose_name=_("options")) picture = FilerImageField( verbose_name=_("picture"), blank=True, null=True, related_name="picture", on_delete=models.SET_NULL, help_text= _("Contact picture. Can be used alongside contact profile, reviews and messages for example." ), ) def __str__(self): return self.full_name class Meta: verbose_name = _("contact") verbose_name_plural = _("contacts") def __init__(self, *args, **kwargs): if self.default_tax_group_getter: kwargs.setdefault("tax_group", self.default_tax_group_getter()) super(Contact, self).__init__(*args, **kwargs) @property def full_name(self): return (" ".join([self.prefix, self.name, self.suffix])).strip() @property def language(self): if self._language is not None: return self._language return configuration.get(None, "default_contact_language", settings.LANGUAGE_CODE) @language.setter def language(self, value): self._language = value def save(self, *args, **kwargs): add_to_default_group = bool(self.pk is None and self.default_contact_group_identifier) super(Contact, self).save(*args, **kwargs) if add_to_default_group: self.groups.add(self.get_default_group()) def get_price_display_options(self, **kwargs): """ Get price display options of the contact. If the default group (`get_default_group`) defines price display options and the contact is member of it, return it. If contact is not (anymore) member of the default group or the default group does not define options, return one of the groups which defines options. If there is more than one such groups, it is undefined which options will be used. If contact is not a member of any group that defines price display options, return default constructed `PriceDisplayOptions`. Subclasses may still override this default behavior. :rtype: PriceDisplayOptions """ group = kwargs.get("group", None) shop = kwargs.get("shop", None) if not group: groups_with_options = self.groups.with_price_display_options(shop) if groups_with_options: default_group = self.default_group if groups_with_options.filter(pk=default_group.pk).exists(): group = default_group else: # Contact was removed from the default group. group = groups_with_options.first() if not group: group = self.default_group return get_price_display_options_for_group_and_shop(group, shop) @classmethod def get_default_group(cls): """ Get or create default contact group for the class. Identifier of the group is specified by the class property `default_contact_group_identifier`. If new group is created, its name is set to value of `default_contact_group_name` class property. :rtype: core.models.ContactGroup """ obj, created = ContactGroup.objects.get_or_create( identifier=cls.default_contact_group_identifier, defaults={"name": cls.default_contact_group_name}) return obj @cached_property def default_group(self): return self.get_default_group() def add_to_shops(self, registration_shop, shops): """ Add contact to multiple shops :param registration_shop: Shop where contact registers. :type registration_shop: core.models.Shop :param shops: A list of shops. :type shops: list :return: """ # set `registration_shop` first to ensure it's being # used if not already set for shop in [registration_shop] + shops: self.add_to_shop(shop) def add_to_shop(self, shop): self.shops.add(shop) if not self.registration_shop: self.registration_shop = shop self.save() def registered_in(self, shop): return self.registration_shop == shop def in_shop(self, shop, only_registration=False): if only_registration: return self.registered_in(shop) if self.shops.filter(pk=shop.pk).exists(): return True return self.registered_in(shop) @property def groups_ids(self): return get_groups_ids(self) if self.pk else [self.default_group.pk]
class Contact(PolymorphicShoopModel): is_anonymous = False is_all_seeing = False default_tax_group_getter = None default_contact_group_identifier = None default_contact_group_name = None created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on')) identifier = InternalIdentifierField(unique=True, null=True, blank=True) is_active = models.BooleanField(default=True, db_index=True, verbose_name=_('active')) # TODO: parent contact? default_shipping_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('shipping address'), on_delete=models.PROTECT) default_billing_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('billing address'), on_delete=models.PROTECT) default_shipping_method = models.ForeignKey( "ShippingMethod", verbose_name=_('default shipping method'), blank=True, null=True, on_delete=models.SET_NULL) default_payment_method = models.ForeignKey( "PaymentMethod", verbose_name=_('default payment method'), blank=True, null=True, on_delete=models.SET_NULL) language = LanguageField(verbose_name=_('language'), blank=True) marketing_permission = models.BooleanField( default=True, verbose_name=_('marketing permission')) phone = models.CharField(max_length=64, blank=True, verbose_name=_('phone')) www = models.URLField(max_length=128, blank=True, verbose_name=_('web address')) timezone = TimeZoneField(blank=True, null=True, verbose_name=_('time zone')) prefix = models.CharField(verbose_name=_('name prefix'), max_length=64, blank=True) name = models.CharField(max_length=256, verbose_name=_('name')) suffix = models.CharField(verbose_name=_('name suffix'), max_length=64, blank=True) name_ext = models.CharField(max_length=256, blank=True, verbose_name=_('name extension')) email = models.EmailField(max_length=256, blank=True, verbose_name=_('email')) tax_group = models.ForeignKey("CustomerTaxGroup", blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('tax group')) merchant_notes = models.TextField(blank=True, verbose_name=_('merchant notes')) def __str__(self): return self.full_name class Meta: verbose_name = _('contact') verbose_name_plural = _('contacts') def __init__(self, *args, **kwargs): if self.default_tax_group_getter: kwargs.setdefault("tax_group", self.default_tax_group_getter()) super(Contact, self).__init__(*args, **kwargs) @property def full_name(self): return (" ".join([self.prefix, self.name, self.suffix])).strip() def get_price_display_options(self): """ Get price display options of the contact. If the default group (`get_default_group`) defines price display options and the contact is member of it, return it. If contact is not (anymore) member of the default group or the default group does not define options, return one of the groups which defines options. If there is more than one such groups, it is undefined which options will be used. If contact is not a member of any group that defines price display options, return default constructed `PriceDisplayOptions`. Subclasses may still override this default behavior. :rtype: PriceDisplayOptions """ groups_with_options = self.groups.with_price_display_options() if groups_with_options: default_group = self.get_default_group() if groups_with_options.filter(pk=default_group.pk).exists(): group_with_options = default_group else: # Contact was removed from the default group. group_with_options = groups_with_options.first() return group_with_options.get_price_display_options() return PriceDisplayOptions() def save(self, *args, **kwargs): add_to_default_group = bool(self.pk is None and self.default_contact_group_identifier) super(Contact, self).save(*args, **kwargs) if add_to_default_group: self.groups.add(self.get_default_group()) @classmethod def get_default_group(cls): """ Get or create default contact group for the class. Identifier of the group is specified by the class property `default_contact_group_identifier`. If new group is created, its name is set to value of `default_contact_group_name` class property. :rtype: core.models.ContactGroup """ obj, created = ContactGroup.objects.get_or_create( identifier=cls.default_contact_group_identifier, defaults={"name": cls.default_contact_group_name}) return obj
class Contact(PolymorphicShuupModel): is_anonymous = False is_all_seeing = False default_tax_group_getter = None default_contact_group_identifier = None default_contact_group_name = None created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on')) modified_on = models.DateTimeField(auto_now=True, editable=False, db_index=True, null=True, verbose_name=_('modified on')) identifier = InternalIdentifierField(unique=True, null=True, blank=True) is_active = models.BooleanField( default=True, db_index=True, verbose_name=_('active'), help_text=_("Check this if the contact is an active customer.")) shops = models.ManyToManyField( "shuup.Shop", blank=True, verbose_name=_('shops'), help_text=_("Inform which shops have access to this contact.")) # TODO: parent contact? default_shipping_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('shipping address'), on_delete=models.PROTECT) default_billing_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('billing address'), on_delete=models.PROTECT) default_shipping_method = models.ForeignKey( "ShippingMethod", verbose_name=_('default shipping method'), blank=True, null=True, on_delete=models.SET_NULL) default_payment_method = models.ForeignKey( "PaymentMethod", verbose_name=_('default payment method'), blank=True, null=True, on_delete=models.SET_NULL) _language = LanguageField( verbose_name=_('language'), blank=True, help_text= _("The primary language to be used in all communications with the contact." )) marketing_permission = models.BooleanField( default=True, verbose_name=_('marketing permission'), help_text= _("Check this if the contact can receive marketing and promotional materials." )) phone = models.CharField( max_length=64, blank=True, verbose_name=_('phone'), help_text=_("The primary phone number of the contact.")) www = models.URLField( max_length=128, blank=True, verbose_name=_('web address'), help_text=_("The web address of the contact, if any.")) timezone = TimeZoneField( blank=True, null=True, verbose_name=_('time zone'), help_text=_( "The timezone in which the contact resides. This can be used to target the delivery of promotional materials " "at a particular time.")) prefix = models.CharField( verbose_name=_('name prefix'), max_length=64, blank=True, help_text=_( "The name prefix of the contact. For example, Mr, Mrs, Dr, etc.")) name = models.CharField(max_length=256, verbose_name=_('name'), help_text=_("The contact name")) suffix = models.CharField( verbose_name=_('name suffix'), max_length=64, blank=True, help_text=_( "The name suffix of the contact. For example, Sr, Jr, etc.")) name_ext = models.CharField(max_length=256, blank=True, verbose_name=_('name extension')) email = models.EmailField( max_length=256, blank=True, verbose_name=_('email'), help_text= _("The email that will receive order confirmations and promotional materials (if permitted)." )) tax_group = models.ForeignKey( "CustomerTaxGroup", blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('tax group'), help_text= _("Select the contact tax group to use for this contact. " "Tax groups can be used to customize the tax rules the that apply to any of this contacts orders. " "Tax groups are defined in Settings - Customer Tax Groups and can be applied to tax rules in " "Settings - Tax Rules")) merchant_notes = models.TextField( blank=True, verbose_name=_('merchant notes'), help_text= _("Enter any private notes for this customer that are only accessible in Shuup admin." )) account_manager = models.ForeignKey("PersonContact", blank=True, null=True, verbose_name=_('account manager')) def __str__(self): return self.full_name class Meta: verbose_name = _('contact') verbose_name_plural = _('contacts') def __init__(self, *args, **kwargs): if self.default_tax_group_getter: kwargs.setdefault("tax_group", self.default_tax_group_getter()) super(Contact, self).__init__(*args, **kwargs) @property def full_name(self): return (" ".join([self.prefix, self.name, self.suffix])).strip() @property def language(self): if self._language is not None: return self._language return configuration.get(None, "default_contact_language", settings.LANGUAGE_CODE) @language.setter def language(self, value): self._language = value def get_price_display_options(self): """ Get price display options of the contact. If the default group (`get_default_group`) defines price display options and the contact is member of it, return it. If contact is not (anymore) member of the default group or the default group does not define options, return one of the groups which defines options. If there is more than one such groups, it is undefined which options will be used. If contact is not a member of any group that defines price display options, return default constructed `PriceDisplayOptions`. Subclasses may still override this default behavior. :rtype: PriceDisplayOptions """ groups_with_options = self.groups.with_price_display_options() if groups_with_options: default_group = self.get_default_group() if groups_with_options.filter(pk=default_group.pk).exists(): group_with_options = default_group else: # Contact was removed from the default group. group_with_options = groups_with_options.first() return group_with_options.get_price_display_options() return PriceDisplayOptions() def save(self, *args, **kwargs): add_to_default_group = bool(self.pk is None and self.default_contact_group_identifier) super(Contact, self).save(*args, **kwargs) if add_to_default_group: self.groups.add(self.get_default_group()) @classmethod def get_default_group(cls): """ Get or create default contact group for the class. Identifier of the group is specified by the class property `default_contact_group_identifier`. If new group is created, its name is set to value of `default_contact_group_name` class property. :rtype: core.models.ContactGroup """ obj, created = ContactGroup.objects.get_or_create( identifier=cls.default_contact_group_identifier, defaults={"name": cls.default_contact_group_name}) return obj
class Venue(models.Model): """A location that serves alcohol""" TAP_LIST_PROVIDERS = ( ("untappd", "Untappd"), ("digitalpour", "DigitalPour"), ("taphunter", "TapHunter"), ("manual", "Chalkboard/Whiteboard"), ("", "Unknown"), ("test", "TEST LOCAL PROVIDER"), ("stemandstein", "The Stem & Stein's HTML"), ("taplist.io", "taplist.io"), ("beermenus", "BeerMenus"), ) # NOTE if this ever grows beyond HSV, we'll have to revisit uniqueness name = models.CharField(max_length=50) address = models.CharField(max_length=50, blank=True) city = models.CharField(max_length=50, blank=True) state = models.CharField(max_length=25, blank=True) postal_code = models.CharField(max_length=10, blank=True) country = CountryField(blank=True) time_zone = TimeZoneField(default=settings.DEFAULT_VENUE_TIME_ZONE) website = models.URLField(blank=True) facebook_page = models.URLField(blank=True) twitter_handle = models.CharField(blank=True, max_length=25) instagram_handle = models.CharField(blank=True, max_length=25) tap_list_provider = models.CharField( "What service the venue uses for digital tap lists", blank=True, max_length=30, choices=TAP_LIST_PROVIDERS, ) untappd_url = models.URLField(blank=True, null=True) email = models.EmailField(blank=True) phone_number = models.CharField(max_length=50, blank=True) logo_url = models.URLField(blank=True) slug = models.SlugField() on_downtown_craft_beer_trail = models.BooleanField(default=False) # -90 to +90 latitude = models.DecimalField( max_digits=10, decimal_places=8, blank=True, null=True, ) # -180 to 180 longitude = models.DecimalField( max_digits=11, decimal_places=8, blank=True, null=True, ) twitter_short_location_description = models.CharField( "Short location description for this specific location for use on " "Twitter", max_length=25, blank=True, ) tap_list_last_check_time = models.DateTimeField( "The last time the venue's tap list was refreshed", blank=True, null=True) tap_list_last_update_time = models.DateTimeField( "The last time the venue's tap list was updated", blank=True, null=True) def __str__(self): return self.name class Meta: constraints = [ models.UniqueConstraint(fields=["name"], name="unique_venue_name"), models.UniqueConstraint(fields=["untappd_url"], name="unique_venue_untappd_url"), ]
class Contact(NameMixin, PolymorphicModel): is_anonymous = False is_all_seeing = False default_tax_group_getter = None default_contact_group_identifier = None default_contact_group_name = None created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on')) identifier = InternalIdentifierField(unique=True, null=True, blank=True) is_active = models.BooleanField(default=True, db_index=True, verbose_name=_('active')) # TODO: parent contact? default_shipping_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('shipping address'), on_delete=models.PROTECT ) default_billing_address = models.ForeignKey( "MutableAddress", null=True, blank=True, related_name="+", verbose_name=_('billing address'), on_delete=models.PROTECT ) default_shipping_method = models.ForeignKey( "ShippingMethod", verbose_name=_('default shipping method'), blank=True, null=True, on_delete=models.SET_NULL ) default_payment_method = models.ForeignKey( "PaymentMethod", verbose_name=_('default payment method'), blank=True, null=True, on_delete=models.SET_NULL ) language = LanguageField(verbose_name=_('language'), blank=True) marketing_permission = models.BooleanField(default=True, verbose_name=_('marketing permission')) phone = models.CharField(max_length=64, blank=True, verbose_name=_('phone')) www = models.URLField(max_length=128, blank=True, verbose_name=_('web address')) timezone = TimeZoneField(blank=True, null=True, verbose_name=_('time zone')) prefix = models.CharField(verbose_name=_('name prefix'), max_length=64, blank=True) name = models.CharField(max_length=256, verbose_name=_('name')) suffix = models.CharField(verbose_name=_('name suffix'), max_length=64, blank=True) name_ext = models.CharField(max_length=256, blank=True, verbose_name=_('name extension')) email = models.EmailField(max_length=256, blank=True, verbose_name=_('email')) tax_group = models.ForeignKey( "CustomerTaxGroup", blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('tax group') ) def __str__(self): return self.full_name class Meta: verbose_name = _('contact') verbose_name_plural = _('contacts') def __init__(self, *args, **kwargs): if self.default_tax_group_getter: kwargs.setdefault("tax_group", self.default_tax_group_getter()) super(Contact, self).__init__(*args, **kwargs) def save(self, *args, **kwargs): add_to_default_group = bool(self.pk is None and self.default_contact_group_identifier) super(Contact, self).save(*args, **kwargs) if add_to_default_group: self.groups.add(self.get_default_group()) @classmethod def get_default_group(cls): """ Get or create default contact group for the class. Identifier of the group is specified by the class property `default_contact_group_identifier`. If new group is created, its name is set to value of `default_contact_group_name` class property. :rtype: core.models.ContactGroup """ obj, created = ContactGroup.objects.get_or_create( identifier=cls.default_contact_group_identifier, defaults={ "name": cls.default_contact_group_name } ) return obj
class Profile(models.Model): user = models.OneToOneField(User, primary_key=True) timezone = TimeZoneField(default=settings.TIME_ZONE)