Exemplo n.º 1
0
class Theme(models.Model):
    name = StringField(_('name'), required=True)
    path = StringField(_('path relative template directory'), required=True)
    settings = JSONField(_('settings'), default={})
    shop = models.ForeignKey('shops.Shop')

    class Meta:
        verbose_name = _('theme')
        verbose_name_plural = _('themes')
        ordering = ('shop', 'name')

    def __str__(self):
        return '{} - {}'.format(self.shop, self.name)
Exemplo n.º 2
0
class CollectionRule(models.Model):
    """
    We include products in collections according to the conditions defined here which
    means that we need to recalculate the collection:ns everytime a product is updated.
    We also need to re calculate whenever a rule is updated.
    """
    collection = models.ForeignKey(CustomCollection, verbose_name=_('collection'))
    column = ChoiceField(_('column'), choices=COLUMN_CHOICES)
    condition = StringField(_('condition'), blank=False)
    position = PositionField()
    relation = ChoiceField(_('relation'), choices=RELATION_CHOICES)

    class Meta:
        unique_together = (('collection', 'column', 'condition', 'relation'))
        ordering = ('position',)
        verbose_name = _('collection rule')
        verbose_name_plural = _('collection rules')

    def __str__(self):
        return '{} - {} {} {}'.format(
            self.collection,
            self.column,
            self.relation,
            self.condition
        )
Exemplo n.º 3
0
class Fulfillment(models.Model):
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    # line items
    notify_customer = models.BooleanField(_('notify customer'), default=False)
    order = models.ForeignKey('orders.Order', verbose_name=_('order'))
    receipt_testcase = models.NullBooleanField(_('reciept testcase'), default=None)
    reciept_autorization = StringField(_('receipt authorization'))
    status = ChoiceField(_('status'), choices=STATUS_CHOICES)
    tracking_company_rel = models.ForeignKey(TrackingCompany, verbose_name=_('tracking company'))
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)

    def tracking_company(self):
        return self.tracking_company_rel.name

    def tracking_numbers(self):
        return list(self.trackingnumber_set.values_list('number', flat=True))

    def tracking_number(self):
        return ', '.join(self.tracking_numbers())

    def tracking_urls(self):
        url = self.tracking_company_rel.tracking_url
        return [url.format(nr) for nr in self.tracking_numbers()]

    def tracking_url(self):
        return ', '.join(self.tracking_url())

    def variant_inventory_management(self):
        raise NotImplemented

    def fulfillment_line_items(self):
        raise NotImplemented

    def item_count(self):
        return len(self.fulfillment_line_items())
Exemplo n.º 4
0
class FulfillmentService(models.Model):
    callback_url = models.URLField(_('callback url'), blank=True, null=True)
    format = ChoiceField(_('format'), choices=FORMAT_CHOICES)
    handle = HandleField(_('handle'), populate_from='name')
    inventory_management = models.BooleanField(_('inventory management'), help_text=_('fulfillment service tracks product inventory'))
    name = StringField(_('name'), required=True)
    provider_id = StringField(_('provider id'), help_text=_('A unique identifier for the fulfillment service provider.'), unique=True)
    requires_shipping_method = models.BooleanField(_('requires shipping method'))
    tracking_support = models.BooleanField(_('tracking support'))

    class Meta:
        ordering = ('name',)
        verbose_name = _('fulfillment service')
        verbose_name_plural = _('fulfillment services')

    def __str__(self):
        return self.name
Exemplo n.º 5
0
class Comment(models.Model):
    article = models.ForeignKey('blogs.Article')
    author = StringField(_('author'))
    body = TextField(_('body'), help_text=_('Markdown of a comment.'))
    body_html = TextField(_('body html'), editable=False)
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    email = models.EmailField(_('email'), blank=True, null=True)
    ip = StringField(_('ip'))
    published_at = models.DateTimeField(_('published at'),
                                        blank=True,
                                        null=True)
    status = ChoiceField(_('status'), choices=STATUS_CHOICES)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)
    user_agent = StringField(_('user agent'))

    class Meta:
        ordering = ('-created_at', )
        verbose_name = _('comment')
        verbose_name_plural = _('commments')

    def __str__(self):
        return '{} - {} - {}'.format(self.article, self.author,
                                     self.created_at.iso_format())

    def get_absolute_url(self):
        return self.url

    @cached_property
    def blog(self):
        return self.article.blog

    @cached_property
    def blog_id(self):
        return self.blog.pk

    @property
    def content(self):
        return self.body_html

    def save(self, **kwargs):
        self.body_html = bleach.clean(markdown.markdown(self.body))
        super().save(**kwargs)

    @cached_property
    def url(self):
        return '{}#{}'.format(self.blog.article.url, self.id)
Exemplo n.º 6
0
class TrackingNumber(models.Model):
    fulfillment_rel = models.ForeignKey(Fulfillment, verbose_name=_('fulfillment'))
    number = StringField(_('number'), required=True)

    class Meta:
        ordering = ('number',)
        verbose_name = _('tracking number')
        verbose_name_plural = _('tracking numbers')

    def __str__(self):
        return self.number
Exemplo n.º 7
0
class Location(models.Model):
    address1 = StringField(_('address'))
    address2 = StringField(_("address con't"))
    city = StringField(_('city'))
    company = StringField(_('company'))
    country = CountryField(_('country'), blank=True, null=True)
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    last_name = StringField(_('last name'))
    name = StringField(_('name'))
    phone = StringField(_('phone'))
    province = StringField(_('region'))
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)
    zip = StringField(_('postal / Zip code'))

    class Meta:
        ordering = ('name', )
        verbose_name = _('location')
        verbose_name_plural = _('locations')

    def __str__(self):
        return self.name
Exemplo n.º 8
0
class ProductImage(MetaFieldsMixin, models.Model):
    alt = StringField(_('alt text'))
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    file = models.ImageField(_('image'), upload_to='products')
    position = PositionField()
    product = models.ForeignKey('products.Product', verbose_name=_('product'))
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)

    class Meta:
        ordering = ('-created_at', )
        verbose_name = _('product image')
        verbose_name_plural = _('product images')

    def __str__(self):
        return self.src

    @cached_property
    def attached_to_variant(self):
        """
        Returns true if the image has been associated with a
        variant. Returns false otherwise. This can be used in cases
        where you want to create a gallery of images that are not
        associated with variants.
        """
        return bool(len(self.productvariant_set.all()))

    def get_absolute_url(self):
        return self.file.url

    @cached_property
    def src(self):
        """
        Returns the relative path of the product image. This is the
        same as outputting {{ image }}
        """
        return self.file.url

    @cached_property
    def variant_ids(self):
        return [v.pk for v in self.variants]

    @cached_property
    def variants(self):
        """
        Returns the variant object(s) that the image is associated with.
        """
        return list(self.productvariant_set.all())
Exemplo n.º 9
0
class ClientDetail(models.Model):
    accept_language = StringField(_('accept language'))
    browser_height = StringField(_('browser height'))
    browser_ip = StringField(_('browser ip'))
    browser_width = StringField(_('browser width'))
    session_hash = StringField(_('sessian hash'))
    user_agent = StringField(_('user agent'))

    class Meta:
        ordering = ('pk',)
        verbose_name = ('client detail')
        verbose_name_plural = ('client details')

    def __str__(self):
        return 'ClientDetail.{}'.format(self.pk)
Exemplo n.º 10
0
class Page(MetaFieldsMixin, models.Model):
    user = models.ForeignKey('users.User', verbose_name=_('author'))
    body_html_t = TransTextField(_('description'))
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    handle_t = TransHandleField(_('handle'), populate_from='title_t', unique_together=('handle_t', 'user'))
    published_at = models.DateTimeField(_('published at'), blank=True, null=True, default=datetime.datetime.now)
    shop = models.ForeignKey('shops.Shop', blank=True, null=True)
    template_suffix = StringField(_('template suffix'))
    title_t = TransStringField(_('title'), required=True)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)

    objects = PageManager()

    class Meta:
        ordering = ('updated_at',)
        verbose_name = _('page')
        verbose_name_plural = _('pages')

    def __str__(self):
        return self.title

    def save(self, **kwargs):
        self.user = get_request().user
        super().save(**kwargs)

    @cached_property
    def author(self):
        return self.user.get_full_name()

    @property
    def content(self):
        return self.body_html

    def get_absolute_url(self):
        return self.url

    @cached_property
    def url(self):
        return '/pages/{}'.format(self.handle)
Exemplo n.º 11
0
class Discount(models.Model):
    applies_once = models.BooleanField(_('limit to one usage'), default=False)
    applies_once_per_customer = models.BooleanField(_('limit 1 per customer'),
                                                    default=False)
    applies_to_id = models.PositiveIntegerField(_('applies to id'),
                                                blank=True,
                                                null=True)
    applies_to_resource = ChoiceField(_('applies to resource'),
                                      choices=RESOURCE_CHOICES)
    code = StringField(_('discount code'), required=True, unique=True)
    discount_type = ChoiceField(_('type'), choices=TYPE_CHOICES)
    ends_at = models.DateField(_('end date'), blank=True, null=True)
    minimum_order_amount = models.FloatField(_('minimum order amount'),
                                             null=True,
                                             blank=True)
    starts_at = models.DateField(_('start date'), blank=True, null=True)
    status = ChoiceField(_('status'), choices=STATUS_CHIOCES)
    times_used = models.PositiveIntegerField(_('used'), blank=True, null=True)
    usage_limit = models.PositiveIntegerField(_('total available'),
                                              blank=True,
                                              null=True)
    value = models.FloatField(_('value'), null=True, blank=True)

    class Meta:
        ordering = ('-starts_at', 'code')
        verbose_name = _('discount')
        verbose_name_plural = _('discounts')

    def __str__(self):
        return self.code

    def amount(self):
        """
        Returns the amount of the discount. Use one of the money filters to
        return the value in a monetary format.
        """
        raise NotImplemented

    @cached_property
    def title(self):
        """
        Alias of the discount code
        """
        return self.code

    def total_amount(self):
        """
        Returns the total amount of the discount if it has been applied to
        multiple line items. Use a money filter to return the value in a
        monetary format.
        """
        raise NotImplemented

    def savings(self):
        """
        Returns the amount of the discount's savings. The negative opposite of
        amount. Use one of the money filters to return the value in a monetary
        format.
        """
        raise NotImplemented

    def total_savings(self):
        """
        Returns the total amount of the discount's savings if it has been
        applied to multiple line items. The negative opposite of total_amount.
        Use a money filter to return the value in a monetary format.
        """
        raise NotImplemented

    @cached_property
    def type(self):
        """
        Returns the type of the discount.
        """
        mapping = {
            'fixed_amount': 'FixedAmountDiscount',
            'percentage': 'PercentageDiscount',
            'shipping': 'ShippingDiscount',
        }
        return mapping[self.discount_type]
Exemplo n.º 12
0
class Order(models.Model):
    billing_address = models.ForeignKey('customers.CustomerAddress', related_name='order_billing_address_set')
    browser_ip = StringField(_('browser ip'))
    buyer_accepts_marketing = models.BooleanField(_('buyer accepts marketing'), default=False)
    cancel_reason = ChoiceField(_('cancel reason'), choices=CANCEL_CHOICES)
    cancelled_at = models.DateTimeField(_('cancelled at'), blank=True, null=True)
    cart_token = StringField(_('cart token'), required=True)
    client_details = models.ForeignKey('orders.ClientDetail', verbose_name=_('client details'))
    closed_at = models.DateTimeField(_('closed at'), blank=True, null=True)
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    currency = ChoiceField(_('currency'), choices=CURRENCY_CHOICES)
    customer = models.ForeignKey('customers.Customer', blank=True, null=True, related_name='order_billing_addresss_set')
    discounts_m2m = models.ManyToManyField('discounts.Discount', blank=True)
    email = models.EmailField(_('email'), blank=True, null=True)
    financial_status = ChoiceField(_('financial status'), choices=FINANCIAL_STATUS_CHOICES)
    fulfillment_status = ChoiceField(_('fulfilment status'), choices=FULFILLMENT_STATUS_CHOICES)
    tags = ArrayField(StringField(_('tag'), required=True), verbose_name=_('tags'), default=[])
    landing_site = models.URLField(_('landing site'), blank=True, null=True)
    # line items
    location = models.ForeignKey('locations.Location', blank=True, null=True)
    order_number = models.PositiveIntegerField(_('order'), editable=False)  # its nice to be searchable
    note = TextField(_('note'))
    note_attributes = JSONField(_('note attributes'))
    processed_at = models.DateTimeField(_('processed at'), blank=True)
    processing_method = ChoiceField(_('processing method'), choices=PROCESSING_CHOICES)
    referring_site = models.URLField(_('referring site'), blank=True, null=True)
    # Do we need to lock this value somehow, infact all related models should be 'locked' on finalization
    shipping_address = models.ForeignKey('customers.CustomerAddress', blank=True, null=True, related_name='order_shipping_address_set')
    source_name = StringField(_('source name'), default='api')
    subtotal_price = models.FloatField(_('subtotal price'))
    taxes_included = models.BooleanField(_('taxes included'))
    token = models.CharField(_('token'), max_length=32, default=get_token, editable=False)
    # All of these total fields are probably better of being calculated values, let's keep
    # normalizing at a minimum since it requires more thinking power
    total_discounts = models.FloatField(_('total discounts'), default=0)
    total_line_items_price = models.FloatField(_('total line items price'))
    total_price = models.FloatField(_('total price'))
    total_tax = models.FloatField(_('total tax'))
    total_weight = models.FloatField(_('total weight'))
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)
    user = models.ForeignKey('users.User', blank=True, null=True)
    order_status_url = models.URLField(_('order status url'), blank=True, null=True)

    @cached_property
    def cancel_reason_label(self):
        return self.get_cancel_reason_label()

    @cached_property
    def cancelled(self):
        return self.cancelled_at is not None

    @cached_property
    def customer_url(self):
        """
        Returns the URL of the customer's account page.
        https://help.shopify.com/themes/liquid/objects/order#order-customer_url
        """
        raise NotImplemented

    @cached_property
    def discounts(self):
        return List(self.discounts_m2m.all())

    @cached_property
    def discount_codes(self):
        codes = []
        for d in self.discouns:
            codes.append({
                'amount': d.amount(),
                'code': d.code,
                'type': d.discount_type,
            })
        return codes

    @cached_property
    def financial_status_label(self):
        return self.get_financial_status_label()

    @cached_property
    def fulfillment_status_label(self):
        return self.get_fulfillment_status_label()

    @cached_property
    def fulfillments(self):
        return List(self.fulfillment_set.all())

    @property
    def gateway(self):
        """
        Deprecated as of July 14, 2014. This information is instead available
        on transactions The payment gateway used.
        """
        return ''

    @cached_property
    def name(self):
        """
        The customer's order name as represented by a number.
        """
        return '#{}'.format(self.number)

    @cached_property
    def number(self):
        """
        Numerical identifier unique to the shop. A number is sequential and
        starts at 1000.

        The example shows number 1, it is a little unclear what this is for.
        """
        return self.pk

    @cached_property
    def payment_details(self):
        """
        Deprecated.
        This information is instead available on transactions An object
        containing information about the payment.
        """
        return {
            "avs_result_code": None,
            "credit_card_bin": None,
            "cvv_result_code": None,
            "credit_card_number": None,
            "credit_card_company": None,
        }

    @cached_property
    def payment_gateway_names(self):
        """
        This should fetch information from the related transaction
        """
        raise NotImplemented

    @cached_property
    def refunds(self):
        """
        The list of refunds applied to the order.
        """
        #return List(self.refund_set.all())
        raise NotImplemented

    def save(self, **kwargs):
        self.order_number = self.pk + 999
        if not self.processed_at:
            self.processed_at = datetime.datetime.now()
        super().save(**kwargs)

    @cached_property
    def shipping_lines(self):
        """
        An array of shipping_line objects, each of which details the shipping
        methods used.

        """
        raise NotImplemented

    @cached_property
    def tax_lines(self):
        """
        An array of tax_line objects, each of which details the total taxes
        applicable to the order.

        https://help.shopify.com/api/reference/order#tax-lines-property
        """
        raise NotImplemented

    @cached_property
    def tax_price(self):
        return self.total_tax
Exemplo n.º 13
0
class Article(MetaFieldsMixin, models.Model):
    user = models.ForeignKey('users.User', verbose_name=_('author'))
    blog = models.ForeignKey('blogs.Blog')
    body_html_t = TransWysiwygField(_('content'))
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    handle_t = TransHandleField(_('handle'),
                                populate_from='title_t',
                                unique_together=('handle_t', 'blog'))
    image = models.ImageField(_('image'),
                              blank=True,
                              null=True,
                              upload_to='blogs')
    published_at = models.DateTimeField(_('published at'),
                                        blank=True,
                                        null=True)
    summary_html_t = TransWysiwygField(_('excerpt'))
    tags_t = TransTagField(_('tags'))
    template_suffix = StringField(_('template suffix'))
    title_t = TransStringField(_('title'), required=True)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)

    objects = ArticleManager()

    class Meta:
        ordering = ('-created_at', )
        verbose_name = _('article')
        verbose_name_plural = _('articles')

    def __str__(self):
        return self.title

    def save(self, **kwargs):
        self.user = get_request().user
        super().save(**kwargs)

    @cached_property
    def author(self):
        """
        Returns the full name of the article's author.
        """
        return self.user.get_full_name()

    @property
    def content(self):
        """
        Returns the content of an article.
        """
        return self.body_html

    @property
    def excerpt(self):
        """
        Returns the excerpt of an article.
        """
        return self.summary_html

    @cached_property
    def excerpt_or_content(self):
        """
        Returns article.excerpt of an article if it exists. Returns
        article.content if an excerpt does not exist for the article.
        """
        return self.excerpt or self.content

    def get_absolute_url(self):
        return self.url

    @cached_property
    def moderated(self):
        """
        Returns true if the blog that the article belongs to is set to moderate
        comments. Returns false if the blog is not moderated.
        """
        return self.blog.moderated

    @cached_property
    def published(self):
        return datetime.datetime.now() >= self.published_at

    @cached_property
    def url(self):
        return '/blogs/{}/{}-{}'.format(self.blog.handle, self.pk, self.handle)
Exemplo n.º 14
0
class CustomCollection(MetaFieldsMixin, models.Model):
    body_html_t = TransWysiwygField(_('description'))
    disjunctive = models.BooleanField(_('products can match any condition'), default=False)
    handle_t = TransHandleField(_('handle'), populate_from='title_t')
    image = ImageField(_('image'), upload_to='products')
    published = models.BooleanField(_('published'), default=True)
    published_at = models.DateTimeField(_('published at'), help_text=_('publish this collection on'), blank=True, null=True)
    published_scope = ChoiceField(_('visability'), choices=PUBLICATION_CHOICES)
    sort_order = ChoiceField(_('sort'), choices=SORT_CHOICES)
    template_suffix = StringField(_('template suffix'))
    title_t = TransStringField(_('title'), required=True)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)

    objects = CustomCollectionManager()

    class Meta:
        ordering = ('-title_t',)
        verbose_name = _('collection')
        verbose_name_plural = _('collections')

    def __str__(self):
        return self.title

    @cached_property
    def all_tags(self):
        """
        Returns a list of all product tags in a collection. collection.all_tags
        will return the full list of tags even when the collection view is
        filtered.

        collection.all_tags will return at most 1,000 tags.

        In comparison, collection.tags returns all tags for a collection for
        the current view. For example, if a collection is filtered by tag,
        collection.tags returns only the tags that match the current filter.
        """
        tags = set()
        for p in self.products:
            tags.union(p.tags)
        return List(sorted(tags))

    @cached_property
    def all_types(self):
        """
        Returns a list of all product types in a collection.
        """
        return List(sorted(filter(None, set(p.product_type for p in self.products))))

    @cached_property
    def all_products_count(self):
        """
        Returns the number of products in a collection.
        collection.all_products_count will return the total number of products
        even when the collection view is filtered.

        In comparison, collection.products_count returns all tags for a
        collection for the current view. For example, if a collection is
        filtered by tag, collection.products_count returns the number of
        products that match the current filter.
        """
        return len(self.products)

    @cached_property
    def all_vendors(self):
        """
        Returns a list of all product vendors in a collection.
        """
        return List(sorted(filter(None, set(p.vendor for p in self.products))))

    def current_type(self):
        """
        Returns the product type on a /collections/types?q=TYPE collection
        page.
        """
        request = get_request()
        if request.path.endswith('/types'):
            return request.GET.get('q')

    def current_vendor(self):
        """
        Returns the product vendor on a /collections/vendors?q=VENDOR
        collection page.
        """
        request = get_request()
        if request.path.endswith('/vendors'):
            return request.GET.get('q')

    @cached_property
    def default_sort_by(self):
        """
        Returns the sort order of the collection, which is set on the
        collection's page in your admin.
        """
        # inconsistant
        # API: https://help.shopify.com/api/reference/customcollection#sort-order-property
        # template: https://help.shopify.com/themes/liquid/objects/collection#collection-default_sort_by
        mapping = {
            'alpha-asc': 'title-descending',
            'alpha-desc': 'title-asc',
            'best-selling': 'best-selling',
            'created': 'created-ascending',
            'created-desc': 'created-descending',
            'manual': 'manual',
            'price-asc': 'price-ascending',
            'price-desc': 'price-descending',
        }
        return mapping[self.sort_order]

    def get_absolute_url(self):
        return '/collection/{}'.format(self.handle)

    @cached_property
    def description(self):
        """
        Alias to body_html
        """
        return self.body_html

    @cached_property
    def adjecant_products(self):
        request = get_request()
        # request.path should look something like: /collections/contemporary-cityscapes/products/auckland
        handle = request.path.split('/')[-1]
        res = {'next': False, 'previous': False}
        for j, p in enumerate(self.products):
            if p.handle == handle:
                if j + 1 < len(self.products):
                    res['next'] = self.products[j + 1]
                if j - 1 >= 0:
                    res['previous'] = self.products[j - 1]
        return res

    @cached_property
    def next_product(self):
        """
        Returns the URL of the next product in the collection. Returns nil if
        there is no next product.

        You can use collection.next_product and collection.previous_product
        with the within filter to create "next" and "previous" links in the
        product template.
        """
        return self.adjecant_products['next']

    @cached_property
    def previous_product(self):
        """
        Returns the URL of the previous product in the collection. Returns nil
        if there is no previous product.

        You can use collection.next_product and collection.previous_product
        with the within filter to create "next" and "previous" links in the
        product template.
        """
        return self.adjecant_products['previous']

    @cached_property
    def products(self):
        """
        Returns all of the products in a collection. You can show a maximum of
        50 products per page.

        Use the paginate tag to choose how many products are shown per page.
        """
        return List(self.product_set.all())

    def products_count(self):
        """
        Returns the number of products in a collection that match the current
        view. For example, if you are viewing a collection filtered by tag,
        collection.products_count will return the number of products that match
        the chosen tag.
        """
        raise NotImplemented

    def tags(self):
        """
        Returns the tags of products in a collection that match the current
        view. For example, if you are viewing a collection filtered by tag,
        collection.tags will return the tags for the products that match the
        current filter.
        """
        raise NotImplemented

    @cached_property
    def url(self):
        return self.get_absolute_url()
Exemplo n.º 15
0
class Shop(MetaFieldsMixin, models.Model):
    address1 = StringField(_('address'))
    address2 = StringField(_("address con't"))
    city = StringField(_('city'))
    county_taxes = models.NullBooleanField(_('county taxes'))
    country_code = CountryField(_('country'), blank=True, null=True)
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    currency = ChoiceField(_('currency'), choices=CURRENCY_CHOICES)
    customer_email = models.EmailField(_('customer email'), blank=True, null=True)
    description = TextField(_('description'))
    domain = StringField(_('domain'))
    email = models.EmailField(_('email'), blank=True, null=True)
    enabled_payment_types = ArrayField(ChoiceField(_('payment type'), choices=PAYMENT_TYPES_CHOICES), verbose_name=_('enabled payment types'), default=[])
    force_ssl = models.BooleanField(_('force ssl'), default=False)
    google_apps_domain = StringField(_('google apps domain'))
    google_apps_login_enabled = models.NullBooleanField(_('google apps login enabled'), default=None)
    iana_timezone = TimeZoneField(_('timezone'))
    latitude = models.FloatField(_('latitude'), blank=True, null=True)
    longitude = models.FloatField(_('longitude'), blank=True, null=True)
    money_format = StringField(_('money format'))
    money_with_currency_format = StringField(_('money with currency format'))
    myshopify_domain = StringField(_('my shopify domain'))
    name = StringField(_('name'))
    navigation = JSONField(_('navigation'), default={})
    password_enabled = models.BooleanField(_('password enabled'), default=False)
    password_message = TextField(_('password message'))
    phone = StringField(_('phone'))
    plan_name = ChoiceField(_('plan name'), choices=PLAN_CHOICES)
    primary_locale = StringField(_('primary locale'), max_length=10)
    permanent_domain = StringField(_('permanent domain'))
    province = StringField(_('region'))
    province_code = StringField(_('region code'))
    shop_owner = StringField(_('shop owner'))
    tax_shipping = models.BooleanField(_('tax shipping'), default=True)
    taxes_included = models.NullBooleanField(_('taxes included'), default=True)
    live_theme = models.ForeignKey('shops.Theme', verbose_name=_('live theme'), blank=True, null=True, related_name='shop_live_theme')
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)
    zip = StringField(_('postal / Zip code'))

    class Meta:
        verbose_name = _('shop')
        verbose_name_plural = _('shops')
        ordering = ('name',)

    def __str__(self):
        return self.name

    @cached_property
    def collections_count(self):
        """
        Returns the number of collections in a shop.
        """
        raise NotImplemented

    @property
    def country(self):
        """
        Alias to country_name
        """
        return self.country_name

    @cached_property
    def country_name(self):
        """
        The shop's normalized country name.
        """
        if self.contry_code:
            return self.country_code.name

    @cached_property
    def country_upper(self):
        """
        Returns the country in the shop's address using uppercase letters.
        """
        return self.country.upper()

    def get_absolute_url(self):
        return self.url

    @cached_property
    def has_discounts(self):
        """
        Indicates if any active discounts exist for the shop.
        """
        raise NotImplemented

    @cached_property
    def has_gift_cards(self):
        """
        Indicates if any active gift cards exist for the shop.
        """
        raise NotImplemented

    @cached_property
    def has_storefront(self):
        """
        Indicates whether the shop has web-based storefront or not.
        """
        raise NotImplemented

    @cached_property
    def locale(self):
        """
        Returns the locale that the shop is currently displayed in (ex: en, fr,
        pt-BR).
        """
        return self.primary_locale

    @cached_property
    def plan_display_name(self):
        """
        The display name of the Shopify plan the shop is on.
        """
        return self.get_plan_name_display()

    @cached_property
    def products_count(self):
        """
        Returns the number of products in a shop.
        """
        raise NotImplemented

    @cached_property
    def setup_required(self):
        """
        Indicates whether the shop has any outstanding setup steps or not.
        """
        raise NotImplemented

    @cached_property
    def secure_url(self):
        """
        Returns the full URL of a shop prepended by the https protocol.
        Example: https://johns-apparel.com
        """
        return 'https://{}'.format(self.domain)

    @cached_property
    def source(self):
        """
        ?
        """
        return None

    @cached_property
    def street(self):
        """
        Returns the combined values of the Address1 and Address2 fields of the
        address.
        """
        return ' '.join(filter(None, self.address1, self.address2))

    @cached_property
    def summary(self):
        """
        Returns a summary of the shop's address:

        150 Elgin Street, Ottawa, Ontario, Canada

        The summary takes the form street, city, state/province, country.
        """
        return ', '.join([self.street, self.city, self.province, self.country])

    @cached_property
    def timezone(self):
        """
        The name of the timezone the shop is in.
        """
        # do something with self.iana_timezone
        raise NotImplemented

    @cached_property
    def types(self):
        """
        Returns an array of all unique product types in a shop.
        """
        raise NotImplemented

    @cached_property
    def url(self):
        """
        Returns the full URL of a shop.
        Example: http://johns-apparel.com
        """
        return 'http://{}'.format(self.domain)

    @cached_property
    def vendors(self):
        """
        Returns an array of all unique vendors in a shop.
        """
        raise NotImplemented
Exemplo n.º 16
0
class Product(MetaFieldsMixin, models.Model):
    body_html_t = TransWysiwygField(_('description'))
    collections_m2m = models.ManyToManyField('products.CustomCollection',
                                             through='products.Collect',
                                             blank=True)
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    handle_t = TransHandleField(_('handle'), populate_from='title_t')
    # sh*pify probably has a related table implementation, is this good enough?
    option1_name_t = TransStringField(_('option #1 name'))
    option2_name_t = TransStringField(_('option #2 name'))
    option3_name_t = TransStringField(_('option #3 name'))
    product_type = StringField(
        _('product type'))  # this might need a related table
    published_at = models.DateTimeField(_('published at'),
                                        help_text=_('publish this product on'),
                                        blank=True,
                                        null=True)
    published_scope = ChoiceField(_('visability'), choices=PUBLICATION_CHOICES)
    tags = ArrayField(StringField(_('tag'), required=True),
                      verbose_name=_('tags'),
                      default=[],
                      blank=True)
    template_suffix = StringField(_('template suffix'))
    title_t = TransStringField(_('title'), required=True)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)
    vendor = StringField(_('vendor'), blank=True, null=True)

    objects = ProductManager()

    class Meta:
        ordering = ('title_t', )
        verbose_name = _('product')
        verbose_name_plural = _('products')

    def __str__(self):
        return self.title

    @cached_property
    def availble(self):
        """
        Returns true if a product is available for purchase. Returns falseif
        all of the products variants' inventory_quantity values are zero or
        less, and their inventory_policy is not set to "Allow users to purchase
        this item, even if it is no longer in stock."
        """
        for v in self.variants():
            if v.availble():
                return True
        return False

    @cached_property
    def compare_at_prices(self):
        return List([v.compare_at_price for v in self.variants()])

    @cached_property
    def compare_at_price_max(self):
        """
        Returns the highest compare at price. Use one of the money filters to
        return the value in a monetary format.
        """
        if self.compare_at_prices:
            return max(self.compare_at_prices)

    @cached_property
    def compare_at_price_min(self):
        """
        Returns the lowest compare at price. Use one of the money filters to
        return the value in a monetary format.
        """
        if self.compare_at_prices:
            return min(self.compare_at_prices)

    @cached_property
    def compare_at_price_varies(self):
        """
        Returns true if the compare_at_price_min is different from the
        compare_at_price_max. Returns false if they are the same.
        """
        return self.compare_at_price_max != self.compare_at_price_min

    @cached_property
    def collections(self):
        return List(self.collections_m2m.all())

    @property
    def content(self):
        """
        Alias for body_html
        """
        return self.body_html

    @property
    def description(self):
        """
        Alias for body_html
        """
        return self.body_html

    @cached_property
    def featured_image(self):
        """
        Returns the relative URL of the product's featured image.
        """
        if self.images:
            return self.image[0].file.url

    @cached_property
    def first_variant(self):
        if self.variants:
            return self.variants[0]

    @cached_property
    def first_available_variant(self):
        """
        Returns the variant object of the first product variant that is
        available for purchase. In order for a variant to be available, its
        variant.inventory_quantity must be greater than zero or
        variant.inventory_policy must be set to continue. A variant with no
        inventory_policy is considered available.
        """
        for v in self.variants:
            if v.available:
                return v

    def get_absolute_url(self):
        return '/products/{}'.format(self.handle)

    @cached_property
    def images(self):
        """
        Returns an array of the product's images. Use the product_img_url
        filter to link to the product image on Shopify's Content Delivery
        Network.
        """
        return List(self.productimage_set.all())

    @cached_property
    def options(self):
        """
        Returns an array of the product's option names.
        """
        names = [self.option1_name, self.option2_name, self.option3_name]
        return List([n for n in names if n])

    @cached_property
    def options_with_values(self):
        """
        Returns an array of the product's options including their available and
        currently selected values.
        """
        opts = List()
        if self.option1_name:
            values = [v.option1 for v in self.variants if v.option1]
            opts.append(Option(self.option1_name, values))
        if self.option2_name:
            values = [v.option2 for v in self.variants if v.option2]
            opts.append(Option(self.option2_name, values))
        if self.option3_name:
            values = [v.option3 for v in self.variants if v.option3]
            opts.append(Option(self.option3_name, values))
        return opts

    @property
    def price(self):
        """
        Alias for price_min
        """
        return self.price_min

    @cached_property
    def price_max(self):
        """
        Returns the highest price of the product. Use one of the money filters
        to return the value in a monetary format.
        """
        if self.prices:
            return max(self.prices)

    @cached_property
    def price_min(self):
        """
        Returns the lowest price of the product. Use one of the money filters
        to return the value in a monetary format.
        """
        if self.prices:
            return min(self.prices)

    @cached_property
    def price_varies(self):
        """
        Returns true if the product's variants have varying prices. Returns
        false if all of the product's variants have the same price.
        """
        return self.price_max != self.price_min

    @cached_property
    def prices(self):
        return [v.price for v in self.variants()]

    @cached_property
    def selected_variant(self):
        """
        Returns the variant object of the currently-selected variant if there
        is a valid ?variant= parameter in the URL. Returns nil if there is not.
        """
        request = get_request()
        variant_id = request.GET.get('variant')
        for v in self.variants:
            if variant_id == v.pk:
                return v

    @cached_property
    def selected_or_first_available_variant(self):
        """
        Returns the variant object of the currently-selected variant if there
        is a valid ?variant= query parameter in the URL. If there is no
        selected variant, the first available variant is returned. In order for
        a variant to be available, its variant.inventory_quantity must be
        greater than zero or variant.inventory_policy must be set to continue.
        A variant with no inventory_management is considered available.
        """
        request = get_request()
        if 'variant' in request.GET:
            return self.selected_variant
        return self.first_available_variant

    @property
    def type(self):
        """
        Alias for product_type
        """
        return self.product_type

    @cached_property
    def url(self):
        """
        Alias for get_absolute_url
        """
        return self.get_absolute_url()

    @cached_property
    def variants(self):
        """
        Returns an array the product's variants.
        """
        return List(self.productvariant_set.all())
Exemplo n.º 17
0
class Blog(MetaFieldsMixin, models.Model):
    commentable = ChoiceField(
        _('commentable'),
        choices=COMMENTABLE_CHOICES,
        help_text=_('Manage how comments are handled on this blog.'))
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    feedburner_location = models.URLField(_('feedburner url'),
                                          blank=True,
                                          null=True)
    handle_t = TransHandleField(_('handle'), populate_from='title_t')
    tags_t = TransTagField(_('tag'))
    template_suffix = StringField(_('template suffix'))
    title_t = TransStringField(_('title'), required=True)

    class Meta:
        ordering = ('title_t', )
        verbose_name = _('blog')
        verbose_name_plural = _('blogs')

    def __str__(self):
        return self.title

    @cached_property
    def all_tags(self):
        """
        Returns all tags of all articles of a blog. This includes tags of
        articles that are not in the current pagination view.
        """
        raise NotImplemented

    @cached_property
    def articles(self):
        """
        Returns an array of all articles in a blog.
        """
        raise NotImplemented
        return List()

    @cached_property
    def articles_count(self):
        """
        Returns the total number of articles in a blog. This total does not
        include hidden articles.
        """
        return len(self.articles)

    @cached_property
    def comments_enabled(self):
        """
        Returns true if comments are enabled, or false if they are disabled.
        """
        return self.commentable != 'no'

    @cached_property
    def feedburner(self):
        if self.feedburner_location:
            return True

    def get_absolute_url(self):
        return self.url

    @cached_property
    def moderated(self):
        """
        Returns true if comments are moderated, or false if they are not
        moderated.
        """
        return self.commentable == 'moderate'

    @cached_property
    def adjecant_articles(self):
        raise NotImplemented

    @cached_property
    def next_article(self):
        """
        Returns the URL of the next (older) post.
        Returns false if there is no next article.
        """
        return self.adjecant_articles['next']

    @cached_property
    def previous_article(self):
        """
        Returns the URL of the previous (newer) post.
        Returns false if there is no next article.
        """
        return self.adjecant_articles['previous']

    @cached_property
    def tags(self):
        """
        Returns all tags in a blog. Similar to all_tags,
        but only returns tags of articles that are in the
        filtered view.
        """
        raise NotImplemented

    @cached_property
    def url(self):
        """
        Returns the relative URL of the blog.
        """
        return '/blogs/{}'.format(self.handle)
Exemplo n.º 18
0
class ProductVariant(MetaFieldsMixin, models.Model):
    barcode = StringField(_('barcode'), help_text=_('ISBN, UPC, GTIN, etc.'))
    compare_at_price = models.FloatField(_('compare at price'),
                                         blank=True,
                                         null=True)
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    fulfillment_service = models.ForeignKey('fulfillments.FulfillmentService',
                                            blank=True,
                                            null=True)
    image = models.ForeignKey('products.ProductImage',
                              verbose_name=_('image'),
                              blank=True,
                              null=True)
    inventory_management = ChoiceField(
        _('track inventory'),
        help_text=_('Sumsum tracks this products inventory'),
        choices=INVENTORY_MANAGEMENT_CHOICES)
    inventory_policy = ChoiceField(
        _('inventory policy'),
        help_text=_(
            "Allow customers to purchase this product when it's out of stock"),
        choices=INVENTORY_POLICY_CHOICES)
    inventory_quantity = models.IntegerField(_('quantity'), default=0)
    next_incoming_date = models.DateField(_('next incoming date'),
                                          blank=True,
                                          null=True)
    option1_t = TransStringField(_('option #1'))
    option2_t = TransStringField(_('option #2'))
    option3_t = TransStringField(_('option #3'))
    position = PositionField()
    price = models.FloatField(_('price'), default=0)
    product = models.ForeignKey('products.Product', verbose_name=_('product'))
    requires_shipping = models.BooleanField(
        _('requires shipping'),
        help_text=_('This product requires shipping'),
        default=False)
    sku = StringField(_('sku'), help_text=_('Stock Keeping Unit'))
    taxable = models.BooleanField(_('taxable'), default=True)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)
    weight_in_unit = models.FloatField(_('weight'), blank=True, null=True)
    weight_unit = ChoiceField(_('weight unit'),
                              choices=UNIT_CHOICES,
                              default='kg')

    class Meta:
        ordering = ('product', )
        verbose_name = _('variant')
        verbose_name_plural = _('variants')

    def __str__(self):
        return self.title

    @cached_property
    def grams(self):
        """
        The weight of the product variant in grams.
        """
        if not self.weight_in_unit:
            return None
        unit_factor = {
            'g': 1,
            'kg': 1000,
            'oz': 28.349523125,
            'lb': 453.59237,
        }
        return unit_factor[self.weight_unit] * self.weight_in_unit

    @cached_property
    def availble(self):
        """
        Returns True if the variant is available for purchase, or False if it
        not. For a variant to be available, its variant.inventory_quantity must
        be greater than zero or variant.inventory_policy must be set to
        continue. A variant with no variant.inventory_management is also
        considered available.
        """
        if self.inventory_management == 'blank' or self.inventory_policy == 'continue':
            return True
        return self.inventory_quantity > 0

    def get_absulute_url(self):
        raise '{}?variant={}'.format(self.product.get_absolute_url(), self.pk)

    @cached_property
    def incoming(self):
        """
        Returns true if the variant has incoming inventory.
        """
        return self.next_incoming_date and self.next_incoming_date >= datetime.date.today(
        )

    def selected(self):
        """
        Returns true if the variant is currently selected by the ?variant= URL
        parameter, or false if it is not.
        """
        request = get_request()
        return self.pk == request.GET.get('variant')

    @cached_property
    def title(self):
        """
        Returns the concatenation of all the variant's option values, joined by
        / characters.
        """
        return ' / '.join(
            filter(None, [self.option1, self.option2, self.option3]))

    @cached_property
    def url(self):
        return self.get_absolute_url()
Exemplo n.º 19
0
class CustomerAddress(models.Model):
    address1 = StringField(_('address'))
    address2 = StringField(_("address con't"))
    city = StringField(_('city'))
    company = StringField(_('company'))
    country_code = CountryField(_('country'), blank=True, null=True)
    customer = models.ForeignKey(Customer,
                                 verbose_name=_('customer'),
                                 blank=True,
                                 null=True)
    first_name = StringField(_('first name'))
    last_name = StringField(_('last name'))
    phone = StringField(_('phone'))
    province = StringField(_('region'))
    province_code = StringField(_('region code'))
    zip = StringField(_('postal / Zip code'))

    class Meta:
        ordering = ('first_name', 'last_name')
        verbose_name = _('customer address')
        verbose_name_plural = _('customer addresses')

    def __str__(self):
        return self.name

    @cached_property
    def country_name(self):
        if self.contry_code:
            return self.country_code.name

    @property
    def country(self):
        """
        Alias to country_name
        """
        return self.country_name

    @cached_property
    def latitude(self):
        """
        https://help.shopify.com/api/reference/order
        latitude: The latitude of the billing address.
        """
        raise NotImplemented

    @cached_property
    def longitude(self):
        """
        https://help.shopify.com/api/reference/order
        The longitude of the billing address.
        """
        raise NotImplemented

    @cached_property
    def name(self):
        """
        Returns the full name
        """
        return ' '.join(filter(None, [self.first_name, self.last_name]))

    @cached_property
    def street(self):
        """
        Returns the combined values of the Address1 and Address2 fields of the
        address.
        """
        return ' '.join(filter(None, self.address1, self.address2))
Exemplo n.º 20
0
class User(MetaFieldsMixin, AbstractBaseUser, PermissionsMixin):
    accepts_marketing = models.BooleanField(_('customer accepts marketing'),
                                            default=False)
    bio = WysiwygField(_('biography'))
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    default_address = models.ForeignKey('customers.CustomerAddress',
                                        verbose_name=_('default address'),
                                        blank=True,
                                        null=True)
    email = models.EmailField(_('email address'), unique=True)
    first_name = StringField(_('first name'))
    homepage = models.URLField(_('homepage'), blank=True, null=True)
    last_name = StringField(_('last name'))
    multipass_identifier = StringField(_('multipass identifier'))
    note = TextField(
        _('notes'),
        help_text=_('Enter any extra notes relating to this customer.'))
    state = ChoiceField(
        _('state'), max_length=50, choices=STATE_CHOICES,
        required=False)  # maybe we need to sync this the is_active field
    tags = ArrayField(StringField(_('tag'), required=True),
                      verbose_name=_('tags'),
                      default=[])
    tax_exempt = models.BooleanField(_('customer is tax excempt'),
                                     default=False)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)
    verified_email = models.BooleanField(_('verified email'), default=False)

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into this admin site.'))
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=
        _('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'
          ))

    USERNAME_FIELD = 'email'
    objects = UserManager()

    class Meta:
        ordering = ('first_name', )
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def get_full_name(self):
        return ' '.join(filter(None, [self.first_name, self.last_name]))

    def get_short_name(self):
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        send_mail(subject, message, from_email, [self.email], **kwargs)

    @cached_property
    def account_owner(self):
        """
        https://help.shopify.com/themes/liquid/objects/article#article-user-account_owner
        Returns "true" if the author of the article is the account owner of the
        shop. Returns "false" if the author is not the account owner.
        """
        raise NotImplemented