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
예제 #2
0
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)
예제 #3
0
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
예제 #4
0
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)
예제 #5
0
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))
예제 #6
0
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 ''
예제 #7
0
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
예제 #8
0
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
예제 #9
0
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]
예제 #10
0
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
예제 #11
0
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
예제 #12
0
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"),
        ]
예제 #13
0
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
예제 #14
0
class Profile(models.Model):
    user = models.OneToOneField(User, primary_key=True)
    timezone = TimeZoneField(default=settings.TIME_ZONE)