def test_autoslugfield_allow_unicode_kwargs_precedence(self): from oscar.models.fields import AutoSlugField with override_settings(OSCAR_SLUG_ALLOW_UNICODE=True): autoslug_field = AutoSlugField(populate_from='title', allow_unicode=False) self.assertFalse(autoslug_field.allow_unicode) autoslug_field = AutoSlugField(populate_from='title') self.assertTrue(autoslug_field.allow_unicode)
def test_migration(self): """ Tests making migrations with Django 1.7+'s migration framework """ import oscar from django.db import migrations from django.db.migrations.writer import MigrationWriter from oscar.models.fields import AutoSlugField fields = { 'autoslugfield': AutoSlugField(populate_from='otherfield'), } migration = type( str("Migration"), (migrations.Migration, ), { "operations": [ migrations.CreateModel("MyModel", tuple( fields.items()), {'populate_from': 'otherfield'}, (models.Model, )), ], }) writer = MigrationWriter(migration) output = writer.as_string() if isinstance(output, str): output = output.encode('utf-8') # We don't test the output formatting - that's too fragile. # Just make sure it runs for now, and that things look alright. context = { 'migrations': migrations, 'oscar': oscar, } result = self.safe_exec(output, context=context) self.assertIn("Migration", result)
class AbstractBase(models.Model): """ Implements the interface declared by shipping.base.Base """ code = AutoSlugField(_("Slug"), max_length=128, unique=True, populate_from='name') name = models.CharField(_("Name"), max_length=128, unique=True) description = models.TextField(_("Description"), blank=True) # We allow shipping methods to be linked to a specific set of countries countries = models.ManyToManyField('address.Country', blank=True, verbose_name=_("Countries")) # We need this to mimic the interface of the Base shipping method is_discounted = False class Meta: abstract = True app_label = 'shipping' ordering = ['name'] verbose_name = _("Shipping Method") verbose_name_plural = _("Shipping Methods") def __str__(self): return self.name
class AbstractBase(models.Model): """ Implements the interface declared by shipping.base.Base """ code = AutoSlugField(_("Slug"), max_length=128, unique=True, populate_from='name') name = models.CharField(_("Name"), max_length=128, unique=True) description = models.TextField(_("Description"), blank=True) # We allow shipping methods to be linked to a specific set of countries countries = models.ManyToManyField('address.Country', null=True, blank=True, verbose_name=_("Countries")) is_discounted = False discount = D('0.00') _basket = None class Meta: abstract = True verbose_name = _("Shipping Method") verbose_name_plural = _("Shipping Methods") ordering = ['name'] def __unicode__(self): return self.name def set_basket(self, basket): self._basket = basket
class AbstractShippingEventType(models.Model): """ A type of shipping/fulfillment event Eg: 'Shipped', 'Cancelled', 'Returned' 一种运输/履行事件 例如:'已发货','已取消','已退回' """ # Name is the friendly description of an event # 名称是对事件的友好描述 name = models.CharField(_("Name"), max_length=255, unique=True) # Code is used in forms # 代码用于表单 code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') class Meta: abstract = True app_label = 'order' verbose_name = _("Shipping Event Type") verbose_name_plural = _("Shipping Event Types") ordering = ('name', ) def __str__(self): return self.name
def test_17_migration(self): """ Tests making migrations with Django 1.7+'s migration framework """ from django.db import migrations from django.db.migrations.writer import MigrationWriter from django.utils import six from oscar.models.fields import AutoSlugField fields = { 'autoslugfield': AutoSlugField(), } migration = type( str("Migration"), (migrations.Migration, ), { "operations": [ migrations.CreateModel("MyModel", tuple( fields.items()), {'populate_from': 'otherfield'}, (models.Model, )), ], }) writer = MigrationWriter(migration) output = writer.as_string() # It should NOT be unicode. self.assertIsInstance(output, six.binary_type, "Migration as_string returned unicode") # We don't test the output formatting - that's too fragile. # Just make sure it runs for now, and that things look alright. result = self.safe_exec(output) self.assertIn("Migration", result)
class ShippingMethod(models.Model): # Fields from shipping.base.ShippingMethod must be added here manually. code = AutoSlugField(_("Slug"), max_length=128, unique=True, populate_from='name') name = models.CharField(_("Name"), max_length=128, unique=True) description = models.TextField(_("Description"), blank=True) # We allow shipping methods to be linked to a specific set of countries countries = models.ManyToManyField('address.Country', null=True, blank=True, verbose_name=_("Countries")) is_discounted = False discount = D('0.00') _basket = None class Meta: abstract = True verbose_name = _("Shipping Method") verbose_name_plural = _("Shipping Methods") def __unicode__(self): return self.name def set_basket(self, basket): self._basket = basket
class AbstractOption(models.Model): """ An option that can be selected for a particular item when the product is added to the basket. For example, a list ID for an SMS message send, or a personalised message to print on a T-shirt. This is not the same as an 'attribute' as options do not have a fixed value for a particular item. Instead, option need to be specified by a customer when they add the item to their basket. """ name = models.CharField(_("Name"), max_length=128) code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') REQUIRED, OPTIONAL = ('Required', 'Optional') TYPE_CHOICES = ( (REQUIRED, _("Required - a value for this option must be specified")), (OPTIONAL, _("Optional - a value for this option can be omitted")), ) type = models.CharField(_("Status"), max_length=128, default=REQUIRED, choices=TYPE_CHOICES) class Meta: abstract = True verbose_name = _("Option") verbose_name_plural = _("Options") def __unicode__(self): return self.name @property def is_required(self): return self.type == self.REQUIRED
class NewAbstractPartner(models.Model): code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') name = models.CharField(pgettext_lazy(u"Partner's name", u"Name"), max_length=128, blank=True) user = models.OneToOneField( AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='partner', null=True, verbose_name=_("User")) is_active = models.BooleanField( _("Is Active?"), default=False) @property def display_name(self): return self.name or self.code @property def primary_address(self): """ Returns a partners primary address. Usually that will be the headquarters or similar. This is a rudimentary implementation that raises an error if there's more than one address. If you actually want to support multiple addresses, you will likely need to extend PartnerAddress to have some field or flag to base your decision on. """ addresses = self.addresses.all() if len(addresses) == 0: # intentionally using len() to save queries return None elif len(addresses) == 1: return addresses[0] else: raise NotImplementedError( "Oscar's default implementation of primary_address only " "supports one PartnerAddress. You need to override the " "primary_address to look up the right address") def get_address_for_stockrecord(self, stockrecord): """ Stock might be coming from different warehouses. Overriding this function allows selecting the correct PartnerAddress for the record. That can be useful when determining tax. """ return self.primary_address class Meta: abstract = True app_label = 'partner' ordering = ('name', 'code') permissions = (('dashboard_access', 'Can access dashboard'), ) verbose_name = _('Fulfillment partner') verbose_name_plural = _('Fulfillment partners') def __str__(self): return self.display_name
class AbstractOption(models.Model): """ An option that can be selected for a particular item when the product is added to the basket. For example, a list ID for an SMS message send, or a personalised message to print on a T-shirt. This is not the same as an 'attribute' as options do not have a fixed value for a particular item. Instead, option need to be specified by a customer when they add the item to their basket. The `type` of the option determines the form input that will be used to collect the information from the customer, and the `required` attribute determines whether a value must be supplied in order to add the item to the basket. """ # Option types TEXT = "text" INTEGER = "integer" FLOAT = "float" BOOLEAN = "boolean" DATE = "date" TYPE_CHOICES = ( (TEXT, _("Text")), (INTEGER, _("Integer")), (BOOLEAN, _("True / False")), (FLOAT, _("Float")), (DATE, _("Date")), ) name = models.CharField(_("Name"), max_length=128, db_index=True) code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') type = models.CharField(_("Type"), max_length=255, default=TEXT, choices=TYPE_CHOICES) required = models.BooleanField(_("Is this option required?"), default=False) class Meta: abstract = True app_label = 'catalogue' ordering = ['name'] verbose_name = _("Option") verbose_name_plural = _("Options") def __str__(self): return self.name @property @deprecated def is_required(self): return self.required
class OfferGroup(models.Model): """ Ordered group of Offers """ name = models.CharField(max_length=64, null=False) slug = AutoSlugField(populate_from='name', unique=True, null=True, default=None) priority = models.IntegerField(null=False, unique=True) is_system_group = models.BooleanField(default=False, editable=False) class Meta: verbose_name = _('OfferGroup') ordering = ('-priority', ) def __str__(self): return '{} (priority {})'.format(self.name, self.priority)
class AbstractCategory(Timestamp): name = models.CharField(max_length=200) slug = AutoSlugField(max_length=128, populate_from='name') class Meta: ordering = ['name'] def __str__(self): return self.name @property def get_number_posts(self): return AbstractCategory.objects.filter( name=self.name, abstractpost__post_date__lte=datetime.date.today()).count()
class AbstractProductClass(models.Model): """ Used for defining options and attributes for a subset of products. E.g. Books, DVDs and Toys. A product can only belong to one product class. At least one product class must be created when setting up a new Oscar deployment. Not necessarily equivalent to top-level categories but usually will be. """ name = models.CharField(_('Name'), max_length=128) slug = AutoSlugField(_('Slug'), max_length=128, unique=True, populate_from='name') #: Some product type don't require shipping (e.g. digital products) - we use #: this field to take some shortcuts in the checkout. requires_shipping = models.BooleanField(_("Requires shipping?"), default=True) #: Digital products generally don't require their stock levels to be #: tracked. track_stock = models.BooleanField(_("Track stock levels?"), default=True) #: These are the options (set by the user when they add to basket) for this #: item class. For instance, a product class of "SMS message" would always #: require a message to be specified before it could be bought. #: Note that you can also set options on a per-product level. options = models.ManyToManyField('catalogue.Option', blank=True, verbose_name=_("Options")) class Meta: abstract = True app_label = 'catalogue' ordering = ['name'] verbose_name = _("Product class") verbose_name_plural = _("Product classes") def __str__(self): return self.name @property def has_attributes(self): return self.attributes.exists()
class AbstractPaymentEventType(models.Model): """ Payment event types are things like 'Paid', 'Failed', 'Refunded'. These are effectively the transaction types. """ name = models.CharField(_("Name"), max_length=128, unique=True) code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') class Meta: abstract = True verbose_name = _("Payment Event Type") verbose_name_plural = _("Payment Event Types") ordering = ('name', ) def __unicode__(self): return self.name
class AbstractSourceType(models.Model): """ A type of payment source. This could be an external partner like PayPal or DataCash, or an internal source such as a managed account. """ name = models.CharField(_("Name"), max_length=128) code = AutoSlugField( _("Code"), max_length=128, populate_from='name', unique=True, help_text=_("This is used within forms to identify this source type")) class Meta: abstract = True app_label = 'payment' verbose_name = _("Source Type") verbose_name_plural = _("Source Types") def __str__(self): return self.name
class AbstractShippingEventType(models.Model): """ A type of shipping/fulfillment event Eg: 'Shipped', 'Cancelled', 'Returned' """ # Name is the friendly description of an event name = models.CharField(_("Name"), max_length=255, unique=True) # Code is used in forms code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') class Meta: abstract = True verbose_name = _("Shipping Event Type") verbose_name_plural = _("Shipping Event Types") ordering = ('name', ) def __unicode__(self): return self.name
class AbstractBase(models.Model): """ Implements the interface declared by shipping.base.Base 实现shipping.base.Base声明的接口 """ code = AutoSlugField(_("Slug"), max_length=128, unique=True, populate_from='name') name = models.CharField(_("Name"), max_length=128, unique=True) description = models.TextField(_("Description"), blank=True) # We allow shipping methods to be linked to a specific set of countries # 我们允许将运输方法链接到特定的一组国家。 countries = models.ManyToManyField('address.Country', blank=True, verbose_name=_("Countries")) # We need this to mimic the interface of the Base shipping method # 我们需要这个来模仿Base运输方法的界面 is_discounted = False class Meta: abstract = True app_label = 'shipping' ordering = ['name'] verbose_name = _("Shipping Method") verbose_name_plural = _("Shipping Methods") def __str__(self): return self.name def discount(self, basket): """ Return the discount on the standard shipping charge 退还标准运费的折扣 """ # This method is identical to the Base.discount(). return D('0.00')
class AbstractPost(Timestamp): title = models.CharField(max_length=200) content = models.TextField(blank=True) featured_image = models.ImageField(_("Featured Image"), upload_to='image') post_date = models.DateField(default=timezone.now) author = models.ForeignKey(AUTH_USER_MODEL, blank=True, null=True, on_delete=models.CASCADE) category = models.ManyToManyField(AbstractCategory, blank=True, through='AbstractCategoryGroup', verbose_name=_("Category")) excerpt = models.TextField(blank=True) slug = AutoSlugField(max_length=128, populate_from='title') class Meta: ordering = ['-post_date', 'title'] def __str__(self): return self.title
class AbstractPaymentEventType(models.Model): """ 支払いイベント管理テーブル 支払いイベントの種類:「支払い済み」、「失敗」、「払い戻し」など These are effectively the transaction types. """ name = models.CharField(_("Name"), max_length=128, unique=True) code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') class Meta: abstract = True app_label = 'order' verbose_name = _("Payment Event Type") verbose_name_plural = _("支払イベント種類管理(支払済/失敗/払戻)") ordering = ('name', ) def __str__(self): return self.name
class AbstractCommunicationEventType(models.Model): """ A 'type' of communication. Like an order confirmation email. """ #: Code used for looking up this event programmatically. # e.g. PASSWORD_RESET. AutoSlugField uppercases the code for us because # it's a useful convention that's been enforced in previous Oscar versions code = AutoSlugField( _('Code'), max_length=128, unique=True, populate_from='name', separator=six.u("_"), uppercase=True, editable=True, validators=[ RegexValidator( regex=r'^[a-zA-Z_][0-9a-zA-Z_]*$', message=_( "Code can only contain the letters a-z, A-Z, digits, " "and underscores, and can't start with a digit."))], help_text=_("Code used for looking up this event programmatically")) #: Name is the friendly description of an event for use in the admin name = models.CharField( _('Name'), max_length=255, help_text=_("This is just used for organisational purposes")) # We allow communication types to be categorised # For backwards-compatibility, the choice values are quite verbose ORDER_RELATED = 'Order related' USER_RELATED = 'User related' CATEGORY_CHOICES = ( (ORDER_RELATED, _('Order related')), (USER_RELATED, _('User related')) ) category = models.CharField( _('Category'), max_length=255, default=ORDER_RELATED, choices=CATEGORY_CHOICES) # Template content for emails # NOTE: There's an intentional distinction between None and ''. None # instructs Oscar to look for a file-based template, '' is just an empty # template. email_subject_template = models.CharField( _('Email Subject Template'), max_length=255, blank=True, null=True) email_body_template = models.TextField( _('Email Body Template'), blank=True, null=True) email_body_html_template = models.TextField( _('Email Body HTML Template'), blank=True, null=True, help_text=_("HTML template")) # Template content for SMS messages sms_template = models.CharField(_('SMS Template'), max_length=170, blank=True, null=True, help_text=_("SMS template")) date_created = models.DateTimeField(_("Date Created"), auto_now_add=True) date_updated = models.DateTimeField(_("Date Updated"), auto_now=True) objects = CommunicationTypeManager() # File templates email_subject_template_file = 'customer/emails/commtype_%s_subject.txt' email_body_template_file = 'customer/emails/commtype_%s_body.txt' email_body_html_template_file = 'customer/emails/commtype_%s_body.html' sms_template_file = 'customer/sms/commtype_%s_body.txt' class Meta: abstract = True app_label = 'customer' verbose_name = _("Communication event type") verbose_name_plural = _("Communication event types") def get_messages(self, ctx=None): """ Return a dict of templates with the context merged in We look first at the field templates but fail over to a set of file templates that follow a conventional path. """ code = self.code.lower() # Build a dict of message name to Template instances templates = {'subject': 'email_subject_template', 'body': 'email_body_template', 'html': 'email_body_html_template', 'sms': 'sms_template'} for name, attr_name in templates.items(): field = getattr(self, attr_name, None) if field is not None: # Template content is in a model field templates[name] = Template(field) else: # Model field is empty - look for a file template template_name = getattr(self, "%s_file" % attr_name) % code try: templates[name] = get_template(template_name) except TemplateDoesNotExist: templates[name] = None # Pass base URL for serving images within HTML emails if ctx is None: ctx = {} ctx['static_base_url'] = getattr( settings, 'OSCAR_STATIC_BASE_URL', None) messages = {} for name, template in templates.items(): messages[name] = template.render(Context(ctx)) if template else '' # Ensure the email subject doesn't contain any newlines messages['subject'] = messages['subject'].replace("\n", "") messages['subject'] = messages['subject'].replace("\r", "") return messages def __str__(self): return self.name def is_order_related(self): return self.category == self.ORDER_RELATED def is_user_related(self): return self.category == self.USER_RELATED
class AbstractPartner(models.Model): """ A fulfilment partner. An individual or company who can fulfil products. E.g. for physical goods, somebody with a warehouse and means of delivery. Creating one or more instances of the Partner model is a required step in setting up an Oscar deployment. Many Oscar deployments will only have one fulfilment partner. """ code = AutoSlugField(_("Code"), max_length=128, unique=True, db_index=True, populate_from='name') name = models.CharField(pgettext_lazy("Partner's name", "Name"), max_length=128, blank=True, db_index=True) #: A partner can have users assigned to it. This is used #: for access modelling in the permission-based dashboard users = models.ManyToManyField(AUTH_USER_MODEL, related_name="partners", blank=True, verbose_name=_("Users")) @property def display_name(self): return self.name or self.code @property def primary_address(self): """ Returns a partners primary address. Usually that will be the headquarters or similar. This is a rudimentary implementation that raises an error if there's more than one address. If you actually want to support multiple addresses, you will likely need to extend PartnerAddress to have some field or flag to base your decision on. """ addresses = self.addresses.all() if len(addresses) == 0: # intentionally using len() to save queries return None elif len(addresses) == 1: return addresses[0] else: raise NotImplementedError( "Oscar's default implementation of primary_address only " "supports one PartnerAddress. You need to override the " "primary_address to look up the right address") def get_address_for_stockrecord(self, stockrecord): """ Stock might be coming from different warehouses. Overriding this function allows selecting the correct PartnerAddress for the record. That can be useful when determining tax. """ return self.primary_address class Meta: abstract = True app_label = 'partner' ordering = ('name', 'code') permissions = (('dashboard_access', 'Can access dashboard'), ) verbose_name = _('Fulfillment partner') verbose_name_plural = _('Fulfillment partners') def __str__(self): return self.display_name
class AbstractHook(models.Model): name = models.CharField(_('Name'), max_length=128) error_report_email = models.EmailField(_('Error Report Email'), blank=False, default='') slug = AutoSlugField(_('Slug'), max_length=255, unique=False, populate_from='name') description = models.TextField(_('Description'), blank=True, null=True) product_class = models.ForeignKey( ProductClass, null=True, blank=True, on_delete=models.PROTECT, verbose_name=_('Product type'), related_name="Product.product_class+", help_text=_("Choose what type of product this is")) user = models.ForeignKey(AUTH_USER_MODEL, related_name="hookClasses", blank=True, verbose_name=_("User"), null=True) date_created = models.DateTimeField(_("Date created"), null=True, default=datetime.now) # This field is used by Haystack to reindex search date_updated = models.DateTimeField(_("Date updated"), db_index=True, null=True, default=datetime.now) event_count = models.PositiveSmallIntegerField(_("HookEvent Count"), default=0) class Meta: abstract = True app_label = 'hooks' ordering = ['-date_created'] verbose_name = _("Hook") verbose_name_plural = _("Hook") def __str__(self): return self.name def save(self, *args, **kwargs): self.slug = slugify(self.name) return super(AbstractHook, self).save(*args, **kwargs) def as_dict(self): return NotImplementedError def get_product_class(self): """ :return: a hook's item class. """ return self.product_class
class SluggedTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title')
class CustomSluggedTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title', separator="_", uppercase=True)
class AbstractPartner(models.Model): """ A fulfillment partner. An individual or company who can fulfil products. E.g. for physical goods, somebody with a warehouse and means of delivery. Creating one or more instances of the Partner model is a required step in setting up an Oscar deployment. Many Oscar deployments will only have one fulfillment partner. 履行合作伙伴。 可以履行产品的个人或公司。 例如。 对于实物商品,有仓库和交货方式的人。 创建合作伙伴模型的一个或多个实例是设置Oscar部署的必要步骤。 许多奥斯卡 部署只会有一个履行合作伙伴。 """ code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') name = models.CharField(pgettext_lazy("Partner's name", "Name"), max_length=128, blank=True) #: A partner can have users assigned to it. This is used #: for access modelling in the permission-based dashboard # 合作伙伴可以为其分配用户。 这用于基于权限的仪表板中的访问建模 users = models.ManyToManyField(AUTH_USER_MODEL, related_name="partners", blank=True, verbose_name=_("Users")) @property def display_name(self): return self.name or self.code @property def primary_address(self): """ Returns a partners primary address. Usually that will be the headquarters or similar. This is a rudimentary implementation that raises an error if there's more than one address. If you actually want to support multiple addresses, you will likely need to extend PartnerAddress to have some field or flag to base your decision on. 返回合作伙伴主要地址。 通常那将是总部或类似的。 这是一个基本实现,如果有多个地址,则会引发错误。 如果您确实希望支持 多个地址,则可能需要扩展PartnerAddress以使某些字段或标记作为您的决策依据。 """ addresses = self.addresses.all() if len( addresses ) == 0: # intentionally using len() to save queries 故意使用len()来保存查询 return None elif len(addresses) == 1: return addresses[0] else: raise NotImplementedError( "Oscar's default implementation of primary_address only " "supports one PartnerAddress. You need to override the " "primary_address to look up the right address") def get_address_for_stockrecord(self, stockrecord): """ Stock might be coming from different warehouses. Overriding this function allows selecting the correct PartnerAddress for the record. That can be useful when determining tax. 库存可能来自不同的仓库。 覆盖此功能允许为记录选择正确的PartnerAddress。 这在确定税时很有用。 """ return self.primary_address class Meta: abstract = True app_label = 'partner' ordering = ('name', 'code') permissions = (('dashboard_access', 'Can access dashboard'), ) verbose_name = _('Fulfillment partner') verbose_name_plural = _('Fulfillment partners') def __str__(self): return self.display_name
class AbstractPartner(models.Model): """ A fulfillment partner. An individual or company who can fulfil products. E.g. for physical goods, somebody with a warehouse and means of delivery. Creating one or more instances of the Partner model is a required step in setting up an Oscar deployment. Many Oscar deployments will only have one fulfillment partner. """ code = AutoSlugField(_("Code"), max_length=128, unique=True, populate_from='name') name = models.CharField(pgettext_lazy(u"Partner's name", u"Name"), max_length=128, blank=True) #: A partner can have users assigned to it. This is used #: for access modelling in the permission-based dashboard users = models.ManyToManyField(AUTH_USER_MODEL, related_name="partners", blank=True, verbose_name=_("Users")) @property def display_name(self): return self.name or self.code @property def primary_address(self): """ Returns a partners primary address. Usually that will be the headquarters or similar. This is a rudimentary implementation that raises an error if there's more than one address. If you actually want to support multiple addresses, you will likely need to extend PartnerAddress to have some field or flag to base your decision on. """ addresses = self.addresses.all() if len(addresses) == 0: # intentionally using len() to save queries return None elif len(addresses) == 1: return addresses[0] else: raise NotImplementedError( "Oscar's default implementation of primary_address only " "supports one PartnerAddress. You need to override the " "primary_address to look up the right address") def get_address_for_stockrecord(self, stockrecord): """ Stock might be coming from different warehouses. Overriding this function allows selecting the correct PartnerAddress for the record. That can be useful when determining tax. """ return self.primary_address class Meta: abstract = True app_label = 'partner' ordering = ('name', 'code') permissions = (('dashboard_access', 'Can access dashboard'), ) verbose_name = _('Fulfillment partner') verbose_name_plural = _('Fulfillment partners') def __str__(self): return self.display_name def get_absolute_url(self): """ Our URL scheme means we have to look up the category's ancestors. As that is a bit more expensive, we cache the generated URL. That is safe even for a stale cache, as the default implementation of ProductCategoryView does the lookup via primary key anyway. But if you change that logic, you'll have to reconsider the caching approach. """ current_locale = get_language() cache_key = 'CATEGORY_URL_%s_%s' % (current_locale, self.pk) url = cache.get(cache_key) url = None if not url: url = reverse('partner:detail', kwargs={ 'product_slug': self.code, 'pk': self.pk }) cache.set(cache_key, url) return url
class AbstractCommunicationEventType(models.Model): """ A 'type' of communication. Like a order confirmation email. """ # Code used for looking up this event programmatically. # eg. PASSWORD_RESET code = AutoSlugField(_('Code'), max_length=128, unique=True, populate_from='name') #: Name is the friendly description of an event for use in the admin name = models.CharField( _('Name'), max_length=255, help_text=_("This is just used for organisational purposes")) # We allow communication types to be categorised ORDER_RELATED = _('Order related') USER_RELATED = _('User related') category = models.CharField(_('Category'), max_length=255, default=ORDER_RELATED) # Template content for emails email_subject_template = models.CharField(_('Email Subject Template'), max_length=255, blank=True, null=True) email_body_template = models.TextField(_('Email Body Template'), blank=True, null=True) email_body_html_template = models.TextField(_('Email Body HTML Template'), blank=True, null=True, help_text=_("HTML template")) # Template content for SMS messages sms_template = models.CharField(_('SMS Template'), max_length=170, blank=True, null=True, help_text=_("SMS template")) date_created = models.DateTimeField(_("Date Created"), auto_now_add=True) date_updated = models.DateTimeField(_("Date Updated"), auto_now=True) objects = CommunicationTypeManager() # File templates email_subject_template_file = 'customer/emails/commtype_%s_subject.txt' email_body_template_file = 'customer/emails/commtype_%s_body.txt' email_body_html_template_file = 'customer/emails/commtype_%s_body.html' sms_template_file = 'customer/sms/commtype_%s_body.txt' class Meta: abstract = True verbose_name = _("Communication event type") verbose_name_plural = _("Communication event types") def get_messages(self, ctx=None): """ Return a dict of templates with the context merged in We look first at the field templates but fail over to a set of file templates that follow a conventional path. """ code = self.code.lower() # Build a dict of message name to Template instances templates = { 'subject': 'email_subject_template', 'body': 'email_body_template', 'html': 'email_body_html_template', 'sms': 'sms_template' } for name, attr_name in templates.items(): field = getattr(self, attr_name, None) if field is not None: # Template content is in a model field templates[name] = Template(field) else: # Model field is empty - look for a file template template_name = getattr(self, "%s_file" % attr_name) % code try: templates[name] = get_template(template_name) except TemplateDoesNotExist: templates[name] = None # Pass base URL for serving images within HTML emails if ctx is None: ctx = {} ctx['static_base_url'] = getattr(settings, 'OSCAR_STATIC_BASE_URL', None) messages = {} for name, template in templates.items(): messages[name] = template.render(Context(ctx)) if template else '' # Ensure the email subject doesn't contain any newlines messages['subject'] = messages['subject'].replace("\n", "") messages['subject'] = messages['subject'].replace("\r", "") return messages def __unicode__(self): return self.name def is_order_related(self): return self.category == self.ORDER_RELATED def is_user_related(self): return self.category == self.USER_RELATED
class AbstractCommunicationEventType(models.Model): """ A 'type' of communication. Like an order confirmation email. 沟通的“类型”。 像订单确认电子邮件。 """ #: Code used for looking up this event programmatically. # e.g. PASSWORD_RESET. AutoSlugField uppercases the code for us because # it's a useful convention that's been enforced in previous Oscar versions # 用于以编程方式查找此事件的代码。 # 例如 重设密码。 AutoSlugField为我们提供了代码,因为它是一个在以前 # 的Oscar版本中强制执行的有用约定 code = AutoSlugField( _('Code'), max_length=128, unique=True, populate_from='name', separator="_", uppercase=True, editable=True, validators=[ RegexValidator( regex=r'^[a-zA-Z_][0-9a-zA-Z_]*$', # 代码只能包含字母a-z,A-Z,数字和下划线,并且不能以数字开头。 message=_( "Code can only contain the letters a-z, A-Z, digits, " "and underscores, and can't start with a digit.")) ], # 用于以编程方式查找此事件的代码 help_text=_("Code used for looking up this event programmatically")) #: Name is the friendly description of an event for use in the admin #: 名称是在管理员中使用的事件的友好描述 name = models.CharField( _('Name'), max_length=255, # 这仅用于组织目的 help_text=_("This is just used for organisational purposes")) # We allow communication types to be categorised # For backwards-compatibility, the choice values are quite verbose # 我们允许对通信类型进行分类为了向后兼容,选择值非常详细 ORDER_RELATED = 'Order related' USER_RELATED = 'User related' CATEGORY_CHOICES = ((ORDER_RELATED, _('Order related')), (USER_RELATED, _('User related'))) category = models.CharField(_('Category'), max_length=255, default=ORDER_RELATED, choices=CATEGORY_CHOICES) # Template content for emails # NOTE: There's an intentional distinction between None and ''. None # instructs Oscar to look for a file-based template, '' is just an empty # template. # 电子邮件的模板内容 # 注意:None和''之间存在故意的区别。 没有指示Oscar寻找基于文件的 # 模板,''只是一个空模板。 email_subject_template = models.CharField(_('Email Subject Template'), max_length=255, blank=True, null=True) email_body_template = models.TextField(_('Email Body Template'), blank=True, null=True) email_body_html_template = models.TextField(_('Email Body HTML Template'), blank=True, null=True, help_text=_("HTML template")) # Template content for SMS messages # SMS消息的模板内容 sms_template = models.CharField(_('SMS Template'), max_length=170, blank=True, null=True, help_text=_("SMS template")) date_created = models.DateTimeField(_("Date Created"), auto_now_add=True) date_updated = models.DateTimeField(_("Date Updated"), auto_now=True) objects = CommunicationTypeManager() # File templates # 文件模板 email_subject_template_file = 'customer/emails/commtype_%s_subject.txt' email_body_template_file = 'customer/emails/commtype_%s_body.txt' email_body_html_template_file = 'customer/emails/commtype_%s_body.html' sms_template_file = 'customer/sms/commtype_%s_body.txt' class Meta: abstract = True app_label = 'customer' verbose_name = _("Communication event type") verbose_name_plural = _("Communication event types") def get_messages(self, ctx=None): """ Return a dict of templates with the context merged in We look first at the field templates but fail over to a set of file templates that follow a conventional path. 返回合并上下文的模板的dict 我们首先查看字段模板,但故障转移到遵循传统路径的一组文件模板。 """ code = self.code.lower() # Build a dict of message name to Template instances # 为Template实例构建消息名称的dict templates = { 'subject': 'email_subject_template', 'body': 'email_body_template', 'html': 'email_body_html_template', 'sms': 'sms_template' } for name, attr_name in templates.items(): field = getattr(self, attr_name, None) if field is not None: # Template content is in a model field # 模板内容位于模型字段中 templates[name] = engines['django'].from_string(field) else: # Model field is empty - look for a file template # 模型字段为空 - 查找文件模板 template_name = getattr(self, "%s_file" % attr_name) % code try: templates[name] = get_template(template_name) except TemplateDoesNotExist: templates[name] = None # Pass base URL for serving images within HTML emails # 传递用于在HTML电子邮件中提供图像的基本URL if ctx is None: ctx = {} ctx['static_base_url'] = getattr(settings, 'OSCAR_STATIC_BASE_URL', None) messages = {} for name, template in templates.items(): messages[name] = template.render(ctx) if template else '' # Ensure the email subject doesn't contain any newlines # 确保电子邮件主题不包含任何换行符 messages['subject'] = messages['subject'].replace("\n", "") messages['subject'] = messages['subject'].replace("\r", "") return messages def __str__(self): return self.name def is_order_related(self): return self.category == self.ORDER_RELATED def is_user_related(self): return self.category == self.USER_RELATED
class News(models.Model): """ News """ author = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_('author'), null=True, blank=True, related_name='news_author') category = models.ForeignKey("NewsCategory", verbose_name=_('category'), related_name="news_category", null=True, blank=True, default=None) meta_description = models.TextField( verbose_name=_('news meta description'), blank=True, default='') meta_keywords = models.TextField(verbose_name=_('news meta keywords'), blank=True, default='') meta_title = models.CharField( verbose_name=_('news meta title'), help_text=_('used in title tag and social sharing'), max_length=255, blank=True, default='') title = models.CharField(_('Title'), max_length=255) slug = AutoSlugField(_('Slug'), max_length=128, unique=True, editable=True, populate_from='title', help_text=_('A slug is a short name which uniquely' ' identifies the news item')) description = HTMLField(_('Description'), blank=True, configuration='CKEDITOR_SETTINGS_NEWS') content = PlaceholderField('news_content', related_name='news_content') publish = models.BooleanField(_('Published'), default=False) date_created = models.DateTimeField(_('created'), auto_now_add=True) date_modified = models.DateTimeField(_('last modified'), auto_now=True) date_published = models.DateTimeField(_('published since'), default=timezone.now) date_published_end = models.DateTimeField(_('published until'), null=True, blank=True) enable_comments = models.BooleanField( verbose_name=_('enable comments on post'), default=ENABLE_COMMENTS) images = models.ManyToManyField( 'filer.Image', through='NewsImages', verbose_name=_("News images"), ) # Oscar links linked_products = models.ManyToManyField( 'catalogue.Product', blank=True, verbose_name=_("Linked products"), help_text=_( "These are products that can be shown with news post " "or news post can be shown on the specific product's page.")) linked_categories = models.ManyToManyField( 'catalogue.Category', blank=True, verbose_name=_("Linked product's categories"), help_text=_("Show news for that categories " "or display news on the category page")) linked_classes = models.ManyToManyField( 'catalogue.ProductClass', blank=True, verbose_name=_("Linked product's classes"), help_text=_("Show news for that classes " "or display news on the specific class product's pages")) sites = models.ManyToManyField( 'sites.Site', verbose_name=_('Site(s)'), blank=True, help_text=_('Select sites in which to show the post. ' 'If none is set it will be ' 'visible in all the configured sites.')) tags = TaggableManager(blank=True, related_name='news_tags') objects = NewsManager() published = PublishedNewsManager() class Meta: app_label = 'oscar_news' verbose_name = _('News') verbose_name_plural = _('News') ordering = ('-date_published', ) def __unicode__(self): return self.title @property def is_published(self): """ Checks whether the news entry is *really* published by checking publishing dates too """ return (self.publish and (self.date_published and self.date_published <= timezone.now()) and (self.date_published_end is None or self.date_published_end > timezone.now())) def _set_default_author(self, current_user): if not self.author_id: if AUTHOR_DEFAULT is True: user = current_user else: user = get_user_model().objects.get(username=AUTHOR_DEFAULT) self.author = user def get_absolute_url(self): """ method below inherited and slightly customized """ cache_key = 'NEWS_ENTRY_URL_%s' % self.pk url = cache.get(cache_key) if not url: # temporarily use link to news detail url = reverse('oscar_news:entry-detail', kwargs={'slug': self.slug}) cache.set(cache_key, url) return url def get_tags(self, queryset=None): """ :return: the list of object's tags annotated with counters. Tags are limited by published news. """ queryset = queryset or News.published.get_queryset() return get_tag_cloud(self.__class__, queryset, set(self.tags.all())) def get_all_tags(self): """ :return: List of all object's tags including unpublished """ return self.get_tags(News.objects.all()) def _get_next_or_previous_published(self, is_next): if not self.pk: raise ValueError( "get_next/get_previous cannot be used on unsaved objects.") op = 'gt' if is_next else 'lt' order = '' if is_next else '-' field = 'date_published' param = force_text(getattr(self, field)) q = Q(**{'%s__%s' % (field, op): param}) q = q | Q(**{field: param, 'pk__%s' % op: self.pk}) qs = self.__class__.published.using(self._state.db).filter(q).order_by( '%s%s' % (order, field), '%spk' % order) try: return qs[0] except IndexError: raise self.DoesNotExist("%s matching query does not exist." % self.__class__._meta.object_name) def get_next_published(self): return self._get_next_or_previous_published(is_next=True) def get_previous_published(self): return self._get_next_or_previous_published(is_next=False)
class AbstractPerson(models.Model): # As it said in wiki there are too many number formats # but no longer than 15 chars (hello, Canada :) # see https://en.wikipedia.org/wiki/VAT_identification_number # for explanation # django-internationalflavor seems to be the right solution # for VAT and BIC fields validation (ACTIVE, LIQUIDATING, LIQUIDATED) = range(1, 4) PERSON_STATUSES = { ACTIVE: _("Active"), LIQUIDATING: _("Liquidating process started"), LIQUIDATED: _("Liquidated"), } vatin = VATNumberField(countries=countries, verbose_name=_("VAT number"), help_text=_("VAT or tax payer ID")) reason_code = models.CharField( _("Code for Reason of registration, e.g. KPP in Russia"), null=True, blank=True, max_length=9) name = models.CharField(_('Name'), max_length=200) full_name = models.CharField(_('Full Name'), max_length=254, blank=True, null=True, ) slug = AutoSlugField(_("Code"), max_length=200, unique=True, populate_from='name') logo = models.ImageField(_('Logo'), upload_to='juristic/logos', blank=True, null=True, max_length=255) image = models.ImageField(_('Image'), upload_to='juristic/images', blank=True, null=True, max_length=255) partner = models.ForeignKey( 'partner.Partner', verbose_name=_("Fulfillment partner"), related_name="juristic_persons", blank=True, null=True,) users = models.ManyToManyField( AUTH_USER_MODEL, related_name="related_persons", blank=True, verbose_name=_("Users")) # Contact details manager_name = models.CharField( _('Manager name'), max_length=200, blank=True, null=True) chief_name = models.CharField( _('GM or Director name'), max_length=200, blank=True, null=True) chief_title = models.CharField( _('Title for GM or Director'), max_length=200, blank=True, null=True) accountant_name = models.CharField( _('Main Accountant name'), max_length=200, blank=True, null=True) phone = models.CharField(_('Phone'), max_length=64, blank=True, null=True) email = models.EmailField(_('Email'), max_length=254, blank=True, null=True) website = models.URLField(_('Web-site'), blank=True, null=True) reference = models.CharField( _("Reference"), max_length=32, unique=True, null=True, blank=True, help_text=_("A reference number that uniquely identifies this person")) description = models.TextField( _("Description"), max_length=2000, blank=True, null=True) group = models.ForeignKey( 'oscar_ficta.PersonGroup', related_name='persons', verbose_name=_("Group"), null=True, blank=True) is_active = models.BooleanField(_("Is active"), default=True) status_choices = [(k, v) for k, v in PERSON_STATUSES.items()] status = models.PositiveIntegerField( _("State status"), choices=status_choices, default=ACTIVE) # Date information date_created = models.DateTimeField( _("Date created"), auto_now_add=True) date_updated = models.DateTimeField( _("Date updated"), auto_now=True, db_index=True) date_registration = models.DateTimeField( _("Date of registration"), blank=True, null=True) date_liquidation = models.DateTimeField( _("Date of liquidation"), blank=True, null=True) # due to https://docs.djangoproject.com/en/1.8/topics/db/managers/#default-managers # default manager should be placed first objects = models.Manager() browsable = BrowsablePersonManager() def __str__(self): return u"%s" % self.name def __unicode__(self): return self.name def current_status(self): return self.PERSON_STATUSES[self.status] class Meta: abstract = True verbose_name = _("Juristic person") verbose_name_plural = _("Juristic persons") unique_together = ("vatin", "reason_code")