Esempio n. 1
0
class JsonModel(models.Model):

    class Meta:
        app_label = 'JsonModel'

    json = JSONField()
    default_json = JSONField(default=lambda: {"check": 12})
Esempio n. 2
0
class ListItem(models.Model):
    llist = models.ForeignKey("List",
                              related_name="items",
                              on_delete=models.CASCADE)

    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    updated_at = models.DateTimeField(_("Updated at"), auto_now=True)
    extra = JSONField(
        verbose_name=_("Arbitrary information for this list item"))

    class Meta:
        verbose_name = _("List item")
        verbose_name_plural = _("List items")

    def __init__(self, *args, **kwargs):
        # reduce the given fields to what the model actually can consume
        super(ListItem, self).__init__(*args, **kwargs)
        self.extra_rows = OrderedDict()
        self._dirty = True

    def save(self, *args, **kwargs):
        super(ListItem, self).save(*args, **kwargs)
        self.llist.save(update_fields=["updated_at"])
        self._dirty = True

    def update(self, request):
        """
        Loop over all registered cart modifier, change the price per cart item and optionally add
        some extra rows.
        """
        if not self._dirty:
            return
        self.extra_rows = OrderedDict()  # reset the dictionary
        self._dirty = False
Esempio n. 3
0
class BaseCartItem(with_metaclass(deferred.ForeignKeyBuilder, models.Model)):
    """
    This is a holder for the quantity of items in the cart and, obviously, a
    pointer to the actual Product being purchased
    """
    cart = deferred.ForeignKey('BaseCart', related_name='items')
    product = deferred.ForeignKey(BaseProduct)
    extra = JSONField(
        verbose_name=_("Arbitrary information for this cart item"))

    objects = CartItemManager()

    class Meta:
        abstract = True
        verbose_name = _("Cart item")
        verbose_name_plural = _("Cart items")

    @classmethod
    def perform_model_checks(cls):
        try:
            allowed_types = ('IntegerField', 'DecimalField', 'FloatField')
            field = [f for f in cls._meta.fields if f.attname == 'quantity'][0]
            if not field.get_internal_type() in allowed_types:
                msg = "Field `{}.quantity` must be of one of the types: {}."
                raise ImproperlyConfigured(
                    msg.format(cls.__name__, allowed_types))
        except IndexError:
            msg = "Class `{}` must implement a field named `quantity`."
            raise ImproperlyConfigured(msg.format(cls.__name__))

    def __init__(self, *args, **kwargs):
        # reduce the given fields to what the model actually can consume
        all_field_names = [
            field.name for field in self._meta.get_fields(include_parents=True)
        ]
        model_kwargs = {
            k: v
            for k, v in kwargs.items() if k in all_field_names
        }
        super(BaseCartItem, self).__init__(*args, **model_kwargs)
        self.extra_rows = OrderedDict()
        self._dirty = True

    def save(self, *args, **kwargs):
        super(BaseCartItem, self).save(*args, **kwargs)
        self._dirty = True
        self.cart._dirty = True

    def update(self, request):
        """
        Loop over all registered cart modifier, change the price per cart item and optionally add
        some extra rows.
        """
        if not self._dirty:
            return
        self.extra_rows = OrderedDict()  # reset the dictionary
        for modifier in cart_modifiers_pool.get_all_modifiers():
            modifier.process_cart_item(self, request)
        self._dirty = False
Esempio n. 4
0
class CartItem(models.Model):
    cart = models.ForeignKey("Cart", related_name="items", on_delete=models.CASCADE)
    product = models.ForeignKey("Product", on_delete=models.CASCADE)

    updated_at = models.DateTimeField(_("Updated at"), auto_now=True)

    extra = JSONField(verbose_name=_("Arbitrary information for this cart item"))

    objects = CartItemManager()

    class Meta:
        verbose_name = _("Cart item")
        verbose_name_plural = _("Cart items")

    def __init__(self, *args, **kwargs):
        # reduce the given fields to what the model actually can consume
        super(CartItem, self).__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        super(CartItem, self).save(*args, **kwargs)
        self.cart.save(update_fields=["updated_at"])
Esempio n. 5
0
class BaseCustomer(with_metaclass(deferred.ForeignKeyBuilder, models.Model)):
    """
    Base class for shop customers.

    Customer is a profile model that extends
    the django User model if a customer is authenticated. On checkout, a User
    object is created for anonymous customers also (with unusable password).
    """
    user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True)
    recognized = CustomerStateField(_("Recognized as"),
                                    help_text=_("Designates the state the customer is recognized as."))
    last_access = models.DateTimeField(_("Last accessed"), default=timezone.now)
    extra = JSONField(editable=False, verbose_name=_("Extra information about this customer"))

    objects = CustomerManager()

    class Meta:
        abstract = True

    def __str__(self):
        return self.get_username()

    def get_username(self):
        return self.user.get_username()

    def get_full_name(self):
        return self.user.get_full_name()

    @property
    def first_name(self):
        warnings.warn("Property first_name is deprecated and will be removed")
        return self.user.first_name

    @first_name.setter
    def first_name(self, value):
        warnings.warn("Property first_name is deprecated and will be removed")
        self.user.first_name = value

    @property
    def last_name(self):
        warnings.warn("Property last_name is deprecated and will be removed")
        return self.user.last_name

    @last_name.setter
    def last_name(self, value):
        warnings.warn("Property last_name is deprecated and will be removed")
        self.user.last_name = value

    @property
    def email(self):
        return self.user.email

    @email.setter
    def email(self, value):
        self.user.email = value

    @property
    def date_joined(self):
        return self.user.date_joined

    @property
    def last_login(self):
        return self.user.last_login

    @property
    def groups(self):
        return self.user.groups

    def is_anonymous(self):
        return self.recognized in (CustomerState.UNRECOGNIZED, CustomerState.GUEST)

    def is_authenticated(self):
        return self.recognized is CustomerState.REGISTERED

    def is_recognized(self):
        """
        Return True if the customer is associated with a User account.
        Unrecognized customers have accessed the shop, but did not register
        an account nor declared themselves as guests.
        """
        return self.recognized is not CustomerState.UNRECOGNIZED

    def is_guest(self):
        """
        Return true if the customer isn't associated with valid User account, but declared
        himself as a guest, leaving their email address.
        """
        return self.recognized is CustomerState.GUEST

    def recognize_as_guest(self, request=None, commit=True):
        """
        Recognize the current customer as guest customer.
        """
        if self.recognized != CustomerState.GUEST:
            self.recognized = CustomerState.GUEST
            if commit:
                self.save(update_fields=['recognized'])
            customer_recognized.send(sender=self.__class__, customer=self, request=request)

    def is_registered(self):
        """
        Return true if the customer has registered himself.
        """
        return self.recognized is CustomerState.REGISTERED

    def recognize_as_registered(self, request=None, commit=True):
        """
        Recognize the current customer as registered customer.
        """
        if self.recognized != CustomerState.REGISTERED:
            self.recognized = CustomerState.REGISTERED
            if commit:
                self.save(update_fields=['recognized'])
            customer_recognized.send(sender=self.__class__, customer=self, request=request)

    def is_visitor(self):
        """
        Always False for instantiated Customer objects.
        """
        return False

    def is_expired(self):
        """
        Return True if the session of an unrecognized customer expired or is not decodable.
        Registered customers never expire.
        Guest customers only expire, if they failed fulfilling the purchase.
        """
        if self.recognized is CustomerState.UNRECOGNIZED:
            try:
                session_key = CustomerManager.decode_session_key(self.user.username)
                return not SessionStore.exists(session_key)
            except KeyError:
                msg = "Unable to decode username '{}' as session key"
                warnings.warn(msg.format(self.user.username))
                return True
        return False

    def get_or_assign_number(self):
        """
        Hook to get or to assign the customers number. It is invoked, every time an Order object
        is created. Using a customer number, which is different from the primary key is useful for
        merchants, wishing to assign sequential numbers only to customers which actually bought
        something. Otherwise the customer number (primary key) is increased whenever a site visitor
        puts something into the cart. If he never proceeds to checkout, that entity expires and may
        be deleted at any time in the future.
        """
        return self.get_number()

    def get_number(self):
        """
        Hook to get the customer's number. Customers haven't purchased anything may return None.
        """
        return str(self.user_id)

    def save(self, **kwargs):
        if 'update_fields' not in kwargs:
            self.user.save(using=kwargs.get('using', DEFAULT_DB_ALIAS))
        super(BaseCustomer, self).save(**kwargs)

    def delete(self, *args, **kwargs):
        if self.user.is_active and self.recognized is CustomerState.UNRECOGNIZED:
            # invalid state of customer, keep the referred User
            super(BaseCustomer, self).delete(*args, **kwargs)
        else:
            # also delete self through cascading
            self.user.delete(*args, **kwargs)
Esempio n. 6
0
class BaseCustomer(with_metaclass(deferred.ForeignKeyBuilder, models.Model)):
    """
    Base class for shop customers.

    Customer is a profile model that extends
    the django User model if a customer is authenticated. On checkout, a User
    object is created for anonymous customers also (with unusable password).
    """
    SALUTATION = (('mrs', _("Mrs.")), ('mr', _("Mr.")), ('na', _("(n/a)")))

    user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True)
    recognized = CustomerStateField(
        _("Recognized as"),
        default=CustomerState.UNRECOGNIZED,
        help_text=_("Designates the state the customer is recognized as."))
    salutation = models.CharField(_("Salutation"),
                                  max_length=5,
                                  choices=SALUTATION)
    last_access = models.DateTimeField(_("Last accessed"),
                                       default=timezone.now)
    extra = JSONField(editable=False,
                      verbose_name=_("Extra information about this customer"))

    objects = CustomerManager()

    class Meta:
        abstract = True

    def __str__(self):
        return self.get_username()

    def get_username(self):
        return self.user.get_username()

    def get_full_name(self):
        return self.user.get_full_name()

    @property
    def first_name(self):
        return self.user.first_name

    @first_name.setter
    def first_name(self, value):
        self.user.first_name = value

    @property
    def last_name(self):
        return self.user.last_name

    @last_name.setter
    def last_name(self, value):
        self.user.last_name = value

    @property
    def email(self):
        return self.user.email

    @email.setter
    def email(self, value):
        self.user.email = value

    @property
    def date_joined(self):
        return self.user.date_joined

    @property
    def last_login(self):
        return self.user.last_login

    @property
    def groups(self):
        return self.user.groups

    def is_anonymous(self):
        return self.recognized in (CustomerState.UNRECOGNIZED,
                                   CustomerState.GUEST)

    def is_authenticated(self):
        return self.recognized is CustomerState.REGISTERED

    def is_recognized(self):
        """
        Return True if the customer is associated with a User account.
        Unrecognized customers have accessed the shop, but did not register
        an account nor declared themselves as guests.
        """
        return self.recognized is not CustomerState.UNRECOGNIZED

    def is_guest(self):
        """
        Return true if the customer isn't associated with valid User account, but declared
        himself as a guest, leaving their email address.
        """
        return self.recognized is CustomerState.GUEST

    def recognize_as_guest(self):
        """
        Recognize the current customer as guest customer.
        """
        self.recognized = CustomerState.GUEST

    def is_registered(self):
        """
        Return true if the customer has registered himself.
        """
        return self.recognized is CustomerState.REGISTERED

    def recognize_as_registered(self):
        """
        Recognize the current customer as registered customer.
        """
        self.recognized = CustomerState.REGISTERED

    def unrecognize(self):
        """
        Unrecognize the current customer.
        """
        self.recognized = CustomerState.UNRECOGNIZED

    def is_visitor(self):
        """
        Always False for instantiated Customer objects.
        """
        return False

    def is_expired(self):
        """
        Return true if the session of an unrecognized customer expired.
        Registered customers never expire.
        Guest customers only expire, if they failed fulfilling the purchase (currently not implemented).
        """
        if self.recognized is CustomerState.UNRECOGNIZED:
            session_key = CustomerManager.decode_session_key(
                self.user.username)
            return not SessionStore.exists(session_key)
        return False

    def get_or_assign_number(self):
        """
        Hook to get or to assign the customers number. It shall be invoked, every time an Order
        object is created. If the customer number shall be different from the primary key, then
        override this method.
        """
        return self.get_number()

    def get_number(self):
        """
        Hook to get the customers number. Customers haven't purchased anything may return None.
        """
        return str(self.user_id)

    def save(self, **kwargs):
        if 'update_fields' not in kwargs:
            self.user.save(using=kwargs.get('using', DEFAULT_DB_ALIAS))
        super(BaseCustomer, self).save(**kwargs)

    def delete(self, *args, **kwargs):
        if self.user.is_active and self.recognized is CustomerState.UNRECOGNIZED:
            # invalid state of customer, keep the referred User
            super(BaseCustomer, self).delete(*args, **kwargs)
        else:
            # also delete self through cascading
            self.user.delete(*args, **kwargs)
Esempio n. 7
0
class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('contenttypes', '0002_remove_content_type_name'),
        ('cms', '0015_auto_20160421_0000'),
        ('email_auth', '0002_auto_20160327_1119'),
        ('filer', '0004_auto_20160328_1434'),
    ]

    operations = [
        migrations.CreateModel(
            name='BillingAddress',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('priority', models.SmallIntegerField(help_text='Priority for using this address')),
                ('addressee', models.CharField(max_length=50, verbose_name='Addressee')),
                ('supplement', models.CharField(blank=True, max_length=50, null=True, verbose_name='Supplement')),
                ('street', models.CharField(max_length=50, verbose_name='Street')),
                ('zip_code', models.CharField(max_length=10, verbose_name='ZIP')),
                ('location', models.CharField(max_length=50, verbose_name='Location')),
                ('country', models.CharField(choices=[('AF', 'Afghanistan'), ('AX', 'Aland Islands'), ('AL', 'Albania'), ('DZ', 'Algeria'), ('AS', 'American Samoa'), ('AD', 'Andorra'), ('AO', 'Angola'), ('AI', 'Anguilla'), ('AQ', 'Antarctica'), ('AG', 'Antigua And Barbuda'), ('AR', 'Argentina'), ('AM', 'Armenia'), ('AW', 'Aruba'), ('AU', 'Australia'), ('AT', 'Austria'), ('AZ', 'Azerbaijan'), ('BS', 'Bahamas'), ('BH', 'Bahrain'), ('BD', 'Bangladesh'), ('BB', 'Barbados'), ('BY', 'Belarus'), ('BE', 'Belgium'), ('BZ', 'Belize'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BT', 'Bhutan'), ('BO', 'Bolivia, Plurinational State Of'), ('BQ', 'Bonaire, Saint Eustatius And Saba'), ('BA', 'Bosnia And Herzegovina'), ('BW', 'Botswana'), ('BV', 'Bouvet Island'), ('BR', 'Brazil'), ('IO', 'British Indian Ocean Territory'), ('BN', 'Brunei Darussalam'), ('BG', 'Bulgaria'), ('BF', 'Burkina Faso'), ('BI', 'Burundi'), ('KH', 'Cambodia'), ('CM', 'Cameroon'), ('CA', 'Canada'), ('CV', 'Cape Verde'), ('KY', 'Cayman Islands'), ('CF', 'Central African Republic'), ('TD', 'Chad'), ('CL', 'Chile'), ('CN', 'China'), ('CX', 'Christmas Island'), ('CC', 'Cocos (Keeling) Islands'), ('CO', 'Colombia'), ('KM', 'Comoros'), ('CG', 'Congo'), ('CD', 'Congo, The Democratic Republic Of The'), ('CK', 'Cook Islands'), ('CR', 'Costa Rica'), ('HR', 'Croatia'), ('CU', 'Cuba'), ('CW', 'Curacao'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DK', 'Denmark'), ('DJ', 'Djibouti'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('EC', 'Ecuador'), ('EG', 'Egypt'), ('SV', 'El Salvador'), ('GQ', 'Equatorial Guinea'), ('ER', 'Eritrea'), ('EE', 'Estonia'), ('ET', 'Ethiopia'), ('FK', 'Falkland Islands (Malvinas)'), ('FO', 'Faroe Islands'), ('FJ', 'Fiji'), ('FI', 'Finland'), ('FR', 'France'), ('GF', 'French Guiana'), ('PF', 'French Polynesia'), ('TF', 'French Southern Territories'), ('GA', 'Gabon'), ('GM', 'Gambia'), ('DE', 'Germany'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GR', 'Greece'), ('GL', 'Greenland'), ('GD', 'Grenada'), ('GP', 'Guadeloupe'), ('GU', 'Guam'), ('GT', 'Guatemala'), ('GG', 'Guernsey'), ('GN', 'Guinea'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HT', 'Haiti'), ('HM', 'Heard Island and McDonald Islands'), ('VA', 'Holy See (Vatican City State)'), ('HN', 'Honduras'), ('HK', 'Hong Kong'), ('HU', 'Hungary'), ('IS', 'Iceland'), ('IN', 'India'), ('ID', 'Indonesia'), ('IR', 'Iran, Islamic Republic Of'), ('IQ', 'Iraq'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IT', 'Italy'), ('CI', 'Ivory Coast'), ('JM', 'Jamaica'), ('JP', 'Japan'), ('JE', 'Jersey'), ('JO', 'Jordan'), ('KZ', 'Kazakhstan'), ('KE', 'Kenya'), ('KI', 'Kiribati'), ('KP', "Korea, Democratic People's Republic Of"), ('KR', 'Korea, Republic Of'), ('KS', 'Kosovo'), ('KW', 'Kuwait'), ('KG', 'Kyrgyzstan'), ('LA', "Lao People's Democratic Republic"), ('LV', 'Latvia'), ('LB', 'Lebanon'), ('LS', 'Lesotho'), ('LR', 'Liberia'), ('LY', 'Libyan Arab Jamahiriya'), ('LI', 'Liechtenstein'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('MO', 'Macao'), ('MK', 'Macedonia'), ('MG', 'Madagascar'), ('MW', 'Malawi'), ('MY', 'Malaysia'), ('MV', 'Maldives'), ('ML', 'Mali'), ('ML', 'Malta'), ('MH', 'Marshall Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MU', 'Mauritius'), ('YT', 'Mayotte'), ('MX', 'Mexico'), ('FM', 'Micronesia'), ('MD', 'Moldova'), ('MC', 'Monaco'), ('MN', 'Mongolia'), ('ME', 'Montenegro'), ('MS', 'Montserrat'), ('MA', 'Morocco'), ('MZ', 'Mozambique'), ('MM', 'Myanmar'), ('NA', 'Namibia'), ('NR', 'Nauru'), ('NP', 'Nepal'), ('NL', 'Netherlands'), ('AN', 'Netherlands Antilles'), ('NC', 'New Caledonia'), ('NZ', 'New Zealand'), ('NI', 'Nicaragua'), ('NE', 'Niger'), ('NG', 'Nigeria'), ('NU', 'Niue'), ('NF', 'Norfolk Island'), ('MP', 'Northern Mariana Islands'), ('NO', 'Norway'), ('OM', 'Oman'), ('PK', 'Pakistan'), ('PW', 'Palau'), ('PS', 'Palestinian Territory, Occupied'), ('PA', 'Panama'), ('PG', 'Papua New Guinea'), ('PY', 'Paraguay'), ('PE', 'Peru'), ('PH', 'Philippines'), ('PN', 'Pitcairn'), ('PL', 'Poland'), ('PT', 'Portugal'), ('PR', 'Puerto Rico'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('BL', 'Saint Barthelemy'), ('SH', 'Saint Helena, Ascension & Tristan Da Cunha'), ('KN', 'Saint Kitts and Nevis'), ('LC', 'Saint Lucia'), ('MF', 'Saint Martin (French Part)'), ('PM', 'Saint Pierre and Miquelon'), ('VC', 'Saint Vincent And The Grenadines'), ('WS', 'Samoa'), ('SM', 'San Marino'), ('ST', 'Sao Tome And Principe'), ('SA', 'Saudi Arabia'), ('SN', 'Senegal'), ('RS', 'Serbia'), ('SC', 'Seychelles'), ('SL', 'Sierra Leone'), ('SG', 'Singapore'), ('SX', 'Sint Maarten (Dutch Part)'), ('SK', 'Slovakia'), ('SI', 'Slovenia'), ('SB', 'Solomon Islands'), ('SO', 'Somalia'), ('ZA', 'South Africa'), ('GS', 'South Georgia And The South Sandwich Islands'), ('ES', 'Spain'), ('LK', 'Sri Lanka'), ('SD', 'Sudan'), ('SR', 'Suriname'), ('SJ', 'Svalbard And Jan Mayen'), ('SZ', 'Swaziland'), ('SE', 'Sweden'), ('CH', 'Switzerland'), ('SY', 'Syrian Arab Republic'), ('TW', 'Taiwan'), ('TJ', 'Tajikistan'), ('TZ', 'Tanzania'), ('TH', 'Thailand'), ('TL', 'Timor-Leste'), ('TG', 'Togo'), ('TK', 'Tokelau'), ('TO', 'Tonga'), ('TT', 'Trinidad and Tobago'), ('TN', 'Tunisia'), ('TR', 'Turkey'), ('TM', 'Turkmenistan'), ('TC', 'Turks And Caicos Islands'), ('TV', 'Tuvalu'), ('UG', 'Uganda'), ('UA', 'Ukraine'), ('AE', 'United Arab Emirates'), ('GB', 'United Kingdom'), ('US', 'United States'), ('UM', 'United States Minor Outlying Islands'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VU', 'Vanuatu'), ('VE', 'Venezuela, Bolivarian Republic Of'), ('VN', 'Viet Nam'), ('VG', 'Virgin Islands, British'), ('VI', 'Virgin Islands, U.S.'), ('WF', 'Wallis and Futuna'), ('EH', 'Western Sahara'), ('YE', 'Yemen'), ('ZM', 'Zambia'), ('ZW', 'Zimbabwe')], max_length=3, verbose_name='Country')),
            ],
            options={
                'verbose_name': 'Billing Address',
                'verbose_name_plural': 'Billing Addresses',
            },
        ),
        migrations.CreateModel(
            name='Cart',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
                ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
                ('extra', JSONField(verbose_name='Arbitrary information for this cart')),
                ('billing_address', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='myshop.BillingAddress')),
            ],
        ),
        migrations.CreateModel(
            name='CartItem',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('extra', JSONField(verbose_name='Arbitrary information for this cart item')),
                ('quantity', models.IntegerField(validators=[django.core.validators.MinValueValidator(0)])),
                ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='myshop.Cart')),
            ],
        ),
        migrations.CreateModel(
            name='Customer',
            fields=[
                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
                ('recognized', shop.models.customer.CustomerStateField(help_text='Designates the state the customer is recognized as.', verbose_name='Recognized as')),
                ('salutation', models.CharField(choices=[('mrs', 'Mrs.'), ('mr', 'Mr.'), ('na', '(n/a)')], max_length=5, verbose_name='Salutation')),
                ('last_access', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Last accessed')),
                ('extra', JSONField(editable=False, verbose_name='Extra information about this customer')),
                ('number', models.PositiveIntegerField(default=None, null=True, unique=True, verbose_name='Customer Number')),
            ],
        ),
        migrations.CreateModel(
            name='Delivery',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('shipping_id', models.CharField(blank=True, help_text="The transaction processor's reference", max_length=255, null=True, verbose_name='Shipping ID')),
                ('fulfilled_at', models.DateTimeField(blank=True, null=True, verbose_name='Fulfilled at')),
                ('shipped_at', models.DateTimeField(blank=True, null=True, verbose_name='Shipped at')),
                ('shipping_method', models.CharField(help_text='The shipping backend used to deliver the items for this order', max_length=50, verbose_name='Shipping method')),
            ],
        ),
        migrations.CreateModel(
            name='DeliveryItem',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('quantity', models.IntegerField(default=0, verbose_name='Delivered quantity')),
                ('delivery', models.ForeignKey(help_text='Refer to the shipping provider used to ship this item', on_delete=django.db.models.deletion.CASCADE, to='myshop.Delivery', verbose_name='Delivery')),
            ],
        ),
        migrations.CreateModel(
            name='Manufacturer',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=50, verbose_name='Name')),
            ],
        ),
        migrations.CreateModel(
            name='OperatingSystem',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=50, verbose_name='Name')),
            ],
        ),
        migrations.CreateModel(
            name='Order',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('status', django_fsm.FSMField(default='new', max_length=50, protected=True, verbose_name='Status')),
                ('currency', models.CharField(editable=False, help_text='Currency in which this order was concluded', max_length=7)),
                ('_subtotal', models.DecimalField(decimal_places=2, max_digits=30, verbose_name='Subtotal')),
                ('_total', models.DecimalField(decimal_places=2, max_digits=30, verbose_name='Total')),
                ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
                ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
                ('extra', JSONField(help_text='Arbitrary information for this order object on the moment of purchase.', verbose_name='Extra fields')),
                ('stored_request', JSONField(help_text='Parts of the Request objects on the moment of purchase.')),
                ('number', models.PositiveIntegerField(default=None, null=True, unique=True, verbose_name='Order Number')),
                ('shipping_address_text', models.TextField(blank=True, help_text='Shipping address at the moment of purchase.', null=True, verbose_name='Shipping Address')),
                ('billing_address_text', models.TextField(blank=True, help_text='Billing address at the moment of purchase.', null=True, verbose_name='Billing Address')),
                ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='myshop.Customer', verbose_name='Customer')),
            ],
            options={
                'verbose_name': 'Order',
                'verbose_name_plural': 'Orders',
            },
            bases=(shop.payment.defaults.PayInAdvanceWorkflowMixin, shop.shipping.delivery.PartialDeliveryWorkflowMixin, shop_stripe.payment.OrderWorkflowMixin, models.Model),
        ),
        migrations.CreateModel(
            name='OrderItem',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('product_name', models.CharField(blank=True, help_text='Product name at the moment of purchase.', max_length=255, null=True, verbose_name='Product name')),
                ('product_code', models.CharField(blank=True, help_text='Product code at the moment of purchase.', max_length=255, null=True, verbose_name='Product code')),
                ('_unit_price', models.DecimalField(decimal_places=2, help_text='Products unit price at the moment of purchase.', max_digits=30, null=True, verbose_name='Unit price')),
                ('_line_total', models.DecimalField(decimal_places=2, help_text='Line total on the invoice at the moment of purchase.', max_digits=30, null=True, verbose_name='Line Total')),
                ('extra', JSONField(help_text='Arbitrary information for this order item', verbose_name='Extra fields')),
                ('quantity', models.IntegerField(verbose_name='Ordered quantity')),
                ('canceled', models.BooleanField(default=False, verbose_name='Item canceled ')),
                ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='myshop.Order', verbose_name='Order')),
            ],
        ),
        migrations.CreateModel(
            name='OrderPayment',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('amount', shop.money.fields.MoneyField(help_text='How much was paid with this particular transfer.', verbose_name='Amount paid')),
                ('transaction_id', models.CharField(help_text="The transaction processor's reference", max_length=255, verbose_name='Transaction ID')),
                ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Received at')),
                ('payment_method', models.CharField(help_text='The payment backend used to process the purchase', max_length=50, verbose_name='Payment method')),
                ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.Order', verbose_name='Order')),
            ],
            options={
                'verbose_name': 'Order payment',
                'verbose_name_plural': 'Order payments',
            },
        ),
        migrations.CreateModel(
            name='Product',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
                ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
                ('active', models.BooleanField(default=True, help_text='Is this product publicly visible.', verbose_name='Active')),
                ('product_name', models.CharField(max_length=255, verbose_name='Product Name')),
                ('slug', models.SlugField(unique=True, verbose_name='Slug')),
                ('order', models.PositiveIntegerField(db_index=True, verbose_name='Sort by')),
            ],
            options={
                'ordering': ('order',),
            },
            bases=(shop.models.product.CMSPageReferenceMixin, parler.models.TranslatableModelMixin, models.Model),
        ),
        migrations.CreateModel(
            name='ProductImage',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('order', models.SmallIntegerField(default=0)),
                ('image', filer.fields.image.FilerImageField(on_delete=django.db.models.deletion.CASCADE, to='filer.Image')),
            ],
            options={
                'ordering': ('order',),
                'abstract': False,
                'verbose_name': 'Product Image',
                'verbose_name_plural': 'Product Images',
            },
        ),
        migrations.CreateModel(
            name='ProductPage',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cms.Page')),
            ],
        ),
        migrations.CreateModel(
            name='ProductTranslation',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')),
                ('caption', djangocms_text_ckeditor.fields.HTMLField(help_text="Short description used in the catalog's list view of products.", verbose_name='Caption')),
            ],
        ),
        migrations.CreateModel(
            name='ShippingAddress',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('priority', models.SmallIntegerField(help_text='Priority for using this address')),
                ('addressee', models.CharField(max_length=50, verbose_name='Addressee')),
                ('supplement', models.CharField(blank=True, max_length=50, null=True, verbose_name='Supplement')),
                ('street', models.CharField(max_length=50, verbose_name='Street')),
                ('zip_code', models.CharField(max_length=10, verbose_name='ZIP')),
                ('location', models.CharField(max_length=50, verbose_name='Location')),
                ('country', models.CharField(choices=[('AF', 'Afghanistan'), ('AX', 'Aland Islands'), ('AL', 'Albania'), ('DZ', 'Algeria'), ('AS', 'American Samoa'), ('AD', 'Andorra'), ('AO', 'Angola'), ('AI', 'Anguilla'), ('AQ', 'Antarctica'), ('AG', 'Antigua And Barbuda'), ('AR', 'Argentina'), ('AM', 'Armenia'), ('AW', 'Aruba'), ('AU', 'Australia'), ('AT', 'Austria'), ('AZ', 'Azerbaijan'), ('BS', 'Bahamas'), ('BH', 'Bahrain'), ('BD', 'Bangladesh'), ('BB', 'Barbados'), ('BY', 'Belarus'), ('BE', 'Belgium'), ('BZ', 'Belize'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BT', 'Bhutan'), ('BO', 'Bolivia, Plurinational State Of'), ('BQ', 'Bonaire, Saint Eustatius And Saba'), ('BA', 'Bosnia And Herzegovina'), ('BW', 'Botswana'), ('BV', 'Bouvet Island'), ('BR', 'Brazil'), ('IO', 'British Indian Ocean Territory'), ('BN', 'Brunei Darussalam'), ('BG', 'Bulgaria'), ('BF', 'Burkina Faso'), ('BI', 'Burundi'), ('KH', 'Cambodia'), ('CM', 'Cameroon'), ('CA', 'Canada'), ('CV', 'Cape Verde'), ('KY', 'Cayman Islands'), ('CF', 'Central African Republic'), ('TD', 'Chad'), ('CL', 'Chile'), ('CN', 'China'), ('CX', 'Christmas Island'), ('CC', 'Cocos (Keeling) Islands'), ('CO', 'Colombia'), ('KM', 'Comoros'), ('CG', 'Congo'), ('CD', 'Congo, The Democratic Republic Of The'), ('CK', 'Cook Islands'), ('CR', 'Costa Rica'), ('HR', 'Croatia'), ('CU', 'Cuba'), ('CW', 'Curacao'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DK', 'Denmark'), ('DJ', 'Djibouti'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('EC', 'Ecuador'), ('EG', 'Egypt'), ('SV', 'El Salvador'), ('GQ', 'Equatorial Guinea'), ('ER', 'Eritrea'), ('EE', 'Estonia'), ('ET', 'Ethiopia'), ('FK', 'Falkland Islands (Malvinas)'), ('FO', 'Faroe Islands'), ('FJ', 'Fiji'), ('FI', 'Finland'), ('FR', 'France'), ('GF', 'French Guiana'), ('PF', 'French Polynesia'), ('TF', 'French Southern Territories'), ('GA', 'Gabon'), ('GM', 'Gambia'), ('DE', 'Germany'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GR', 'Greece'), ('GL', 'Greenland'), ('GD', 'Grenada'), ('GP', 'Guadeloupe'), ('GU', 'Guam'), ('GT', 'Guatemala'), ('GG', 'Guernsey'), ('GN', 'Guinea'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HT', 'Haiti'), ('HM', 'Heard Island and McDonald Islands'), ('VA', 'Holy See (Vatican City State)'), ('HN', 'Honduras'), ('HK', 'Hong Kong'), ('HU', 'Hungary'), ('IS', 'Iceland'), ('IN', 'India'), ('ID', 'Indonesia'), ('IR', 'Iran, Islamic Republic Of'), ('IQ', 'Iraq'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IT', 'Italy'), ('CI', 'Ivory Coast'), ('JM', 'Jamaica'), ('JP', 'Japan'), ('JE', 'Jersey'), ('JO', 'Jordan'), ('KZ', 'Kazakhstan'), ('KE', 'Kenya'), ('KI', 'Kiribati'), ('KP', "Korea, Democratic People's Republic Of"), ('KR', 'Korea, Republic Of'), ('KS', 'Kosovo'), ('KW', 'Kuwait'), ('KG', 'Kyrgyzstan'), ('LA', "Lao People's Democratic Republic"), ('LV', 'Latvia'), ('LB', 'Lebanon'), ('LS', 'Lesotho'), ('LR', 'Liberia'), ('LY', 'Libyan Arab Jamahiriya'), ('LI', 'Liechtenstein'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('MO', 'Macao'), ('MK', 'Macedonia'), ('MG', 'Madagascar'), ('MW', 'Malawi'), ('MY', 'Malaysia'), ('MV', 'Maldives'), ('ML', 'Mali'), ('ML', 'Malta'), ('MH', 'Marshall Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MU', 'Mauritius'), ('YT', 'Mayotte'), ('MX', 'Mexico'), ('FM', 'Micronesia'), ('MD', 'Moldova'), ('MC', 'Monaco'), ('MN', 'Mongolia'), ('ME', 'Montenegro'), ('MS', 'Montserrat'), ('MA', 'Morocco'), ('MZ', 'Mozambique'), ('MM', 'Myanmar'), ('NA', 'Namibia'), ('NR', 'Nauru'), ('NP', 'Nepal'), ('NL', 'Netherlands'), ('AN', 'Netherlands Antilles'), ('NC', 'New Caledonia'), ('NZ', 'New Zealand'), ('NI', 'Nicaragua'), ('NE', 'Niger'), ('NG', 'Nigeria'), ('NU', 'Niue'), ('NF', 'Norfolk Island'), ('MP', 'Northern Mariana Islands'), ('NO', 'Norway'), ('OM', 'Oman'), ('PK', 'Pakistan'), ('PW', 'Palau'), ('PS', 'Palestinian Territory, Occupied'), ('PA', 'Panama'), ('PG', 'Papua New Guinea'), ('PY', 'Paraguay'), ('PE', 'Peru'), ('PH', 'Philippines'), ('PN', 'Pitcairn'), ('PL', 'Poland'), ('PT', 'Portugal'), ('PR', 'Puerto Rico'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('BL', 'Saint Barthelemy'), ('SH', 'Saint Helena, Ascension & Tristan Da Cunha'), ('KN', 'Saint Kitts and Nevis'), ('LC', 'Saint Lucia'), ('MF', 'Saint Martin (French Part)'), ('PM', 'Saint Pierre and Miquelon'), ('VC', 'Saint Vincent And The Grenadines'), ('WS', 'Samoa'), ('SM', 'San Marino'), ('ST', 'Sao Tome And Principe'), ('SA', 'Saudi Arabia'), ('SN', 'Senegal'), ('RS', 'Serbia'), ('SC', 'Seychelles'), ('SL', 'Sierra Leone'), ('SG', 'Singapore'), ('SX', 'Sint Maarten (Dutch Part)'), ('SK', 'Slovakia'), ('SI', 'Slovenia'), ('SB', 'Solomon Islands'), ('SO', 'Somalia'), ('ZA', 'South Africa'), ('GS', 'South Georgia And The South Sandwich Islands'), ('ES', 'Spain'), ('LK', 'Sri Lanka'), ('SD', 'Sudan'), ('SR', 'Suriname'), ('SJ', 'Svalbard And Jan Mayen'), ('SZ', 'Swaziland'), ('SE', 'Sweden'), ('CH', 'Switzerland'), ('SY', 'Syrian Arab Republic'), ('TW', 'Taiwan'), ('TJ', 'Tajikistan'), ('TZ', 'Tanzania'), ('TH', 'Thailand'), ('TL', 'Timor-Leste'), ('TG', 'Togo'), ('TK', 'Tokelau'), ('TO', 'Tonga'), ('TT', 'Trinidad and Tobago'), ('TN', 'Tunisia'), ('TR', 'Turkey'), ('TM', 'Turkmenistan'), ('TC', 'Turks And Caicos Islands'), ('TV', 'Tuvalu'), ('UG', 'Uganda'), ('UA', 'Ukraine'), ('AE', 'United Arab Emirates'), ('GB', 'United Kingdom'), ('US', 'United States'), ('UM', 'United States Minor Outlying Islands'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VU', 'Vanuatu'), ('VE', 'Venezuela, Bolivarian Republic Of'), ('VN', 'Viet Nam'), ('VG', 'Virgin Islands, British'), ('VI', 'Virgin Islands, U.S.'), ('WF', 'Wallis and Futuna'), ('EH', 'Western Sahara'), ('YE', 'Yemen'), ('ZM', 'Zambia'), ('ZW', 'Zimbabwe')], max_length=3, verbose_name='Country')),
                ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.Customer')),
            ],
            options={
                'verbose_name': 'Shipping Address',
                'verbose_name_plural': 'Shipping Addresses',
            },
        ),
        migrations.CreateModel(
            name='SmartCardTranslation',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')),
                ('description', djangocms_text_ckeditor.fields.HTMLField(help_text="Full description used in the catalog's detail view of Smart Cards.", verbose_name='Description')),
            ],
            options={
                'managed': True,
                'db_table': 'myshop_smartcard_translation',
                'db_tablespace': '',
                'default_permissions': (),
                'verbose_name': 'Smart Card Translation',
            },
        ),
        migrations.CreateModel(
            name='SmartPhone',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('product_code', models.CharField(max_length=255, unique=True, verbose_name='Product code')),
                ('unit_price', shop.money.fields.MoneyField(decimal_places=3, help_text='Net price for this product', verbose_name='Unit price')),
                ('storage', models.PositiveIntegerField(help_text='Internal storage in MB', verbose_name='Internal Storage')),
            ],
        ),
        migrations.CreateModel(
            name='SmartPhoneModelTranslation',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')),
                ('description', djangocms_text_ckeditor.fields.HTMLField(help_text="Full description used in the catalog's detail view of Smart Cards.", verbose_name='Description')),
            ],
            options={
                'managed': True,
                'db_table': 'myshop_smartphonemodel_translation',
                'db_tablespace': '',
                'default_permissions': (),
                'verbose_name': 'Smart Phone Translation',
            },
        ),
        migrations.CreateModel(
            name='Commodity',
            fields=[
                ('product_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='myshop.Product')),
                ('unit_price', shop.money.fields.MoneyField(decimal_places=3, help_text='Net price for this product', verbose_name='Unit price')),
                ('product_code', models.CharField(max_length=255, unique=True, verbose_name='Product code')),
                ('placeholder', cms.models.fields.PlaceholderField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, slotname='Commodity Details', to='cms.Placeholder')),
            ],
            options={
                'verbose_name': 'Commodity',
                'verbose_name_plural': 'Commodities',
            },
            bases=('myshop.product',),
        ),
        migrations.CreateModel(
            name='SmartCard',
            fields=[
                ('product_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='myshop.Product')),
                ('unit_price', shop.money.fields.MoneyField(decimal_places=3, help_text='Net price for this product', verbose_name='Unit price')),
                ('card_type', models.CharField(choices=[('SD', 'SD'), ('micro SD', 'micro SD'), ('SDXC', 'SDXC'), ('micro SDXC', 'micro SDXC'), ('SDHC', 'SDHC'), ('micro SDHC', 'micro SDHC'), ('SDHC II', 'SDHC II'), ('micro SDHC II', 'micro SDHC II')], max_length=15, verbose_name='Card Type')),
                ('speed', models.CharField(choices=[(b'4', '4 MB/s'), (b'20', '20 MB/s'), (b'30', '30 MB/s'), (b'40', '40 MB/s'), (b'48', '48 MB/s'), (b'80', '80 MB/s'), (b'95', '95 MB/s'), (b'280', '280 MB/s')], max_length=8, verbose_name='Transfer Speed')),
                ('product_code', models.CharField(max_length=255, unique=True, verbose_name='Product code')),
                ('storage', models.PositiveIntegerField(help_text='Storage capacity in GB', verbose_name='Storage Capacity')),
            ],
            options={
                'verbose_name': 'Smart Card',
                'verbose_name_plural': 'Smart Cards',
            },
            bases=('myshop.product',),
        ),
        migrations.CreateModel(
            name='SmartPhoneModel',
            fields=[
                ('product_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='myshop.Product')),
                ('battery_type', models.PositiveSmallIntegerField(choices=[(1, 'Lithium Polymer (Li-Poly)'), (2, 'Lithium Ion (Li-Ion)')], verbose_name='Battery type')),
                ('battery_capacity', models.PositiveIntegerField(help_text='Battery capacity in mAh', verbose_name='Capacity')),
                ('ram_storage', models.PositiveIntegerField(help_text='RAM storage in MB', verbose_name='RAM')),
                ('wifi_connectivity', models.PositiveIntegerField(choices=[(1, '802.11 b/g/n')], help_text='WiFi Connectivity', verbose_name='WiFi')),
                ('bluetooth', models.PositiveIntegerField(choices=[(1, 'Bluetooth 4.0')], help_text='Bluetooth Connectivity', verbose_name='Bluetooth')),
                ('gps', models.BooleanField(default=False, help_text='GPS integrated', verbose_name='GPS')),
                ('width', models.DecimalField(decimal_places=1, help_text='Width in mm', max_digits=4, verbose_name='Width')),
                ('height', models.DecimalField(decimal_places=1, help_text='Height in mm', max_digits=4, verbose_name='Height')),
                ('weight', models.DecimalField(decimal_places=1, help_text='Weight in gram', max_digits=5, verbose_name='Weight')),
                ('screen_size', models.DecimalField(decimal_places=2, help_text='Diagonal screen size in inch', max_digits=4, verbose_name='Screen size')),
                ('operating_system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.OperatingSystem', verbose_name='Operating System')),
            ],
            options={
                'verbose_name': 'Smart Phone',
                'verbose_name_plural': 'Smart Phones',
            },
            bases=('myshop.product',),
        ),
        migrations.AddField(
            model_name='producttranslation',
            name='master',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='myshop.Product'),
        ),
        migrations.AddField(
            model_name='productpage',
            name='product',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.Product'),
        ),
        migrations.AddField(
            model_name='productimage',
            name='product',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.Product'),
        ),
        migrations.AddField(
            model_name='product',
            name='cms_pages',
            field=models.ManyToManyField(help_text='Choose list view this product shall appear on.', through='myshop.ProductPage', to='cms.Page'),
        ),
        migrations.AddField(
            model_name='product',
            name='images',
            field=models.ManyToManyField(through='myshop.ProductImage', to='filer.Image'),
        ),
        migrations.AddField(
            model_name='product',
            name='manufacturer',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.Manufacturer', verbose_name='Manufacturer'),
        ),
        migrations.AddField(
            model_name='product',
            name='polymorphic_ctype',
            field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_myshop.product_set+', to='contenttypes.ContentType'),
        ),
        migrations.AddField(
            model_name='orderitem',
            name='product',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='myshop.Product', verbose_name='Product'),
        ),
        migrations.AddField(
            model_name='deliveryitem',
            name='item',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.OrderItem', verbose_name='Ordered item'),
        ),
        migrations.AddField(
            model_name='delivery',
            name='order',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.Order'),
        ),
        migrations.AddField(
            model_name='cartitem',
            name='product',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.Product'),
        ),
        migrations.AddField(
            model_name='cart',
            name='customer',
            field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='cart', to='myshop.Customer', verbose_name='Customer'),
        ),
        migrations.AddField(
            model_name='cart',
            name='shipping_address',
            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='myshop.ShippingAddress'),
        ),
        migrations.AddField(
            model_name='billingaddress',
            name='customer',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.Customer'),
        ),
        migrations.AddField(
            model_name='smartphonemodeltranslation',
            name='master',
            field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='multilingual', to='myshop.SmartPhoneModel'),
        ),
        migrations.AddField(
            model_name='smartphone',
            name='product',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='myshop.SmartPhoneModel', verbose_name='Smart-Phone Model'),
        ),
        migrations.AddField(
            model_name='smartcardtranslation',
            name='master',
            field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='multilingual', to='myshop.SmartCard'),
        ),
        migrations.AlterUniqueTogether(
            name='producttranslation',
            unique_together=set([('language_code', 'master')]),
        ),
        migrations.AlterUniqueTogether(
            name='smartphonemodeltranslation',
            unique_together=set([('language_code', 'master')]),
        ),
        migrations.AlterUniqueTogether(
            name='smartcardtranslation',
            unique_together=set([('language_code', 'master')]),
        ),
    ]
Esempio n. 8
0
class BaseCart(with_metaclass(deferred.ForeignKeyBuilder, models.Model)):
    """
    The fundamental part of a shopping cart.
    """
    customer = deferred.OneToOneField(
        'BaseCustomer',
        verbose_name=_("Customer"),
        related_name='cart',
    )

    created_at = models.DateTimeField(
        _("Created at"),
        auto_now_add=True,
    )

    updated_at = models.DateTimeField(
        _("Updated at"),
        auto_now=True,
    )

    extra = JSONField(verbose_name=_("Arbitrary information for this cart"))

    # our CartManager determines the cart object from the request.
    objects = CartManager()

    class Meta:
        abstract = True
        verbose_name = _("Shopping Cart")
        verbose_name_plural = _("Shopping Carts")

    def __init__(self, *args, **kwargs):
        super(BaseCart, self).__init__(*args, **kwargs)
        # That will hold things like tax totals or total discount
        self.extra_rows = OrderedDict()
        self._cached_cart_items = None
        self._dirty = True

    def save(self, force_update=False, *args, **kwargs):
        if self.pk or force_update is False:
            super(BaseCart, self).save(force_update=force_update,
                                       *args,
                                       **kwargs)
        self._dirty = True

    def update(self, request):
        """
        This should be called after a cart item changed quantity, has been added or removed.

        It will loop on all line items in the cart, and call all the cart modifiers for each item.
        After doing this, it will compute and update the order's total and subtotal fields, along
        with any supplement added along the way by modifiers.

        Note that theses added fields are not stored - we actually want to
        reflect rebate and tax changes on the *cart* items, but we don't want
        that for the order items (since they are legally binding after the
        "purchase" button was pressed)
        """
        if not self._dirty:
            return

        if self._cached_cart_items:
            items = self._cached_cart_items
        else:
            items = CartItemModel.objects.filter_cart_items(self, request)

        # This calls all the pre_process_cart methods and the pre_process_cart_item for each item,
        # before processing the cart. This allows to prepare and collect data on the cart.
        for modifier in cart_modifiers_pool.get_all_modifiers():
            modifier.pre_process_cart(self, request)
            for item in items:
                modifier.pre_process_cart_item(self, item, request)

        self.extra_rows = OrderedDict()  # reset the dictionary
        self.subtotal = 0  # reset the subtotal
        for item in items:
            # item.update iterates over all cart modifiers and invokes method `process_cart_item`
            item.update(request)
            self.subtotal += item.line_total

        # Iterate over the registered modifiers, to process the cart's summary
        for modifier in cart_modifiers_pool.get_all_modifiers():
            for item in items:
                modifier.post_process_cart_item(self, item, request)
            modifier.process_cart(self, request)

        # This calls the post_process_cart method from cart modifiers, if any.
        # It allows for a last bit of processing on the "finished" cart, before
        # it is displayed
        for modifier in reversed(cart_modifiers_pool.get_all_modifiers()):
            modifier.post_process_cart(self, request)

        # Cache updated cart items
        self._cached_cart_items = items
        self._dirty = False

    def empty(self):
        """
        Remove the cart with all its items.
        """
        if self.pk:
            self.items.all().delete()
            self.delete()

    def merge_with(self, other_cart):
        """
        Merge the contents of the other cart into this one, afterwards delete it.
        This is done item by item, so that duplicate items increase the quantity.
        """
        # iterate over the cart and add quantities for items from other cart considered as equal
        for item in self.items.all():
            other_item = item.product.is_in_cart(other_cart, extra=item.extra)
            if other_item:
                item.quantity += other_item.quantity
                item.save()
                other_item.delete()

        # the remaining items from the other cart are merged into this one
        other_cart.items.update(cart=self)
        other_cart.delete()

    def __str__(self):
        return "{}".format(self.pk) if self.pk else '(unsaved)'

    @property
    def num_items(self):
        """
        Returns the number of items in the cart.
        """
        return self.items.filter(quantity__gt=0).count()

    @property
    def total_quantity(self):
        """
        Returns the total quantity of all items in the cart.
        """
        aggr = self.items.aggregate(quantity=models.Sum('quantity'))
        return aggr['quantity'] or 0
        # if we would know, that self.items is already evaluated, then this might be faster:
        # return sum([ci.quantity for ci in self.items.all()])

    @property
    def is_empty(self):
        return self.total_quantity == 0

    def get_caption_data(self):
        return {
            'num_items': self.num_items,
            'total_quantity': self.total_quantity,
            'subtotal': self.subtotal,
            'total': self.total
        }

    @classmethod
    def get_default_caption_data(cls):
        return {
            'num_items': 0,
            'total_quantity': 0,
            'subtotal': Money(),
            'total': Money()
        }
Esempio n. 9
0
class Cart(models.Model):
    """
    The fundamental part of a shopping cart.
    """

    customer = models.OneToOneField(
        "Customer",
        verbose_name=_("Customer"),
        related_name="cart",
        on_delete=models.CASCADE,
    )
    products = models.ManyToManyField(
        "Product", related_name="carts", through="CartItem"
    )
    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
    updated_at = models.DateTimeField(_("Updated at"), auto_now=True)
    extra = JSONField(verbose_name=_("Arbitrary information for this cart"))

    # our CartManager determines the cart object from the request.
    objects = CartManager()

    class Meta:
        verbose_name = _("Shopping Cart")
        verbose_name_plural = _("Shopping Carts")

    def __init__(self, *args, **kwargs):
        super(Cart, self).__init__(*args, **kwargs)
        # That will hold things like tax totals or total discount
        self.extra_rows = OrderedDict()
        self._cached_cart_items = None
        self._dirty = True

    def save(self, force_update=False, *args, **kwargs):
        if self.pk or force_update is False:
            super(Cart, self).save(force_update=force_update, *args, **kwargs)
        self._dirty = True

    def update(self, request):
        """
        This should be called after a cart item changed quantity, has been added or removed.
        It will loop over all items in the cart, and call all the configured cart modifiers.
        After this is done, it will compute and update the order's total and subtotal fields, along
        with any supplement added along the way by modifiers.
        Note that theses added fields are not stored - we actually want to
        reflect rebate and tax changes on the *cart* items, but we don't want
        that for the order items (since they are legally binding after the
        "purchase" button was pressed)
        """
        if not self._dirty:
            return

        if self._cached_cart_items:
            items = self._cached_cart_items
        else:
            items = CartItem.objects.filter_cart_items(self, request)

        self.extra_rows = OrderedDict()  # reset the dictionary
        self.subtotal = 0  # reset the subtotal
        for item in items:
            # item.update iterates over all cart modifiers and invokes method `process_cart_item`
            item.update(request)
            self.subtotal += item.line_total

        # Cache updated cart items
        self._cached_cart_items = items
        self._dirty = False

    def empty(self):
        """
        Remove the cart with all its items.
        """
        if self.pk:
            self.items.all().delete()
            self.delete()

    def merge_with(self, other_cart):
        """
        Merge the contents of the other cart into this one, afterwards delete it.
        This is done item by item, so that duplicate items increase the quantity.
        """
        # iterate over the cart and add quantities for items from other cart considered as equal
        if self.id == other_cart.id:
            raise RuntimeError("Can not merge cart with itself")
        for item in self.items.all():
            other_item = item.product.is_in_cart(other_cart, extra=item.extra)
            if other_item:
                item.quantity += other_item.quantity
                item.save()
                other_item.delete()

        # the remaining items from the other cart are merged into this one
        other_cart.items.update(cart=self)
        other_cart.delete()

    def __str__(self):
        return "{}".format(self.pk) if self.pk else "(unsaved)"

    @property
    def num_items(self):
        """
        Returns the number of items in the cart.
        """
        return self.items.filter(quantity__gt=0).count()

    @property
    def total_quantity(self):
        """
        Returns the total quantity of all items in the cart.
        """
        aggr = self.items.aggregate(quantity=models.Sum("quantity"))
        return aggr["quantity"] or 0
        # if we would know, that self.items is already evaluated, then this might be faster:
        # return sum([ci.quantity for ci in self.items.all()])

    @property
    def is_empty(self):
        return self.num_items == 0 and self.total_quantity == 0
Esempio n. 10
0
class OrderItem(models.Model):
    """
    An item for an order.
    """

    order = models.ForeignKey(Order,
                              related_name="items",
                              verbose_name=_("Order"),
                              on_delete=models.CASCADE)

    product_name = models.CharField(
        _("Product name"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Product name at the moment of purchase."),
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Product code at the moment of purchase."),
    )

    product = models.ForeignKey(
        Product,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        verbose_name=_("Product"),
    )

    _unit_price = models.DecimalField(
        _("Unit price"),
        null=True,  # may be NaN
        help_text=_("Products unit price at the moment of purchase."),
        **Order.decimalfield_kwargs)

    _line_total = models.DecimalField(
        _("Line Total"),
        null=True,  # may be NaN
        help_text=_("Line total on the invoice at the moment of purchase."),
        **Order.decimalfield_kwargs)

    extra = JSONField(
        verbose_name=_("Extra fields"),
        help_text=_("Arbitrary information for this order item"),
    )

    class Meta:
        verbose_name = pgettext_lazy("order_models", "Ordered Item")
        verbose_name_plural = pgettext_lazy("order_models", "Ordered Items")

    def __str__(self):
        return self.product_name

    @property
    def unit_price(self):
        return self._unit_price
        # MoneyMaker(self.order.currency)(self._unit_price)

    @property
    def line_total(self):
        return self._line_total
        # MoneyMaker(self.order.currency)(self._line_total)

    # def populate_from_cart_item(self, cart_item, request):
    #     """
    #     From a given cart item, populate the current order item.
    #     If the operation was successful, the given item shall be removed from the cart.
    #     If a CartItem.DoesNotExist exception is raised, discard the order item.
    #     """
    #     if cart_item.quantity == 0:
    #         raise CartItem.DoesNotExist("Cart Item is on the Wish List")
    #     self.product = cart_item.product
    #     # for historical integrity, store the product's name and price at the moment of purchase
    #     self.product_name = cart_item.product.product_name
    #     self.product_code = cart_item.product_code
    #     self._unit_price = Decimal(cart_item.unit_price)
    #     self._line_total = Decimal(cart_item.line_total)
    #     self.quantity = cart_item.quantity
    #     self.extra = dict(cart_item.extra)
    #     extra_rows = [(modifier, extra_row.data) for modifier, extra_row in cart_item.extra_rows.items()]
    #     self.extra.update(rows=extra_rows)

    def save(self, *args, **kwargs):
        """
        Before saving the OrderItem object to the database, round the amounts to the given decimal places
        """
        self._unit_price = Order.round_amount(self._unit_price)
        self._line_total = Order.round_amount(self._line_total)
        super(OrderItem, self).save(*args, **kwargs)
Esempio n. 11
0
class BaseOrderItem(models.Model, metaclass=deferred.ForeignKeyBuilder):
    """
    An item for an order.
    """
    order = deferred.ForeignKey(
        BaseOrder,
        on_delete=models.CASCADE,
        related_name='items',
        verbose_name=_("Order"),
    )

    product_name = models.CharField(
        _("Product name"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Product name at the moment of purchase."),
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Product code at the moment of purchase."),
    )

    product = deferred.ForeignKey(
        BaseProduct,
        on_delete=models.SET_NULL,
        verbose_name=_("Product"),
        null=True,
        blank=True,
    )

    _unit_price = models.DecimalField(
        _("Unit price"),
        null=True,  # may be NaN
        help_text=_("Products unit price at the moment of purchase."),
        **BaseOrder.decimalfield_kwargs)

    _line_total = models.DecimalField(
        _("Line Total"),
        null=True,  # may be NaN
        help_text=_("Line total on the invoice at the moment of purchase."),
        **BaseOrder.decimalfield_kwargs)

    extra = JSONField(
        verbose_name=_("Extra fields"),
        help_text=_("Arbitrary information for this order item"),
    )

    class Meta:
        abstract = True
        verbose_name = pgettext_lazy('order_models', "Ordered Item")
        verbose_name_plural = pgettext_lazy('order_models', "Ordered Items")

    def __str__(self):
        return self.product_name

    @classmethod
    def check(cls, **kwargs):
        errors = super().check(**kwargs)
        for cart_field in CartItemModel._meta.fields:
            if cart_field.attname == 'quantity':
                break
        else:
            msg = "Class `{}` must implement a field named `quantity`."
            errors.append(checks.Error(msg.format(CartItemModel.__name__)))
        for field in cls._meta.fields:
            if field.attname == 'quantity':
                if field.get_internal_type() != cart_field.get_internal_type():
                    msg = "Field `{}.quantity` must be of same type as `{}.quantity`."
                    errors.append(
                        checks.Error(
                            msg.format(cls.__name__, CartItemModel.__name__)))
                break
        else:
            msg = "Class `{}` must implement a field named `quantity`."
            errors.append(checks.Error(msg.format(cls.__name__)))
        return errors

    @property
    def unit_price(self):
        return MoneyMaker(self.order.currency)(self._unit_price)

    @property
    def line_total(self):
        return MoneyMaker(self.order.currency)(self._line_total)

    def populate_from_cart_item(self, cart_item, request):
        """
        From a given cart item, populate the current order item.
        If the operation was successful, the given item shall be removed from the cart.
        If an exception of type :class:`CartItem.DoesNotExist` is raised, discard the order item.
        """
        if cart_item.quantity == 0:
            raise CartItemModel.DoesNotExist("Cart Item is on the Wish List")
        kwargs = {'product_code': cart_item.product_code}
        kwargs.update(cart_item.extra)
        cart_item.product.deduct_from_stock(cart_item.quantity, **kwargs)
        self.product = cart_item.product
        # for historical integrity, store the product's name and price at the moment of purchase
        self.product_name = cart_item.product.product_name
        self.product_code = cart_item.product_code
        self._unit_price = Decimal(cart_item.unit_price)
        self._line_total = Decimal(cart_item.line_total)
        self.quantity = cart_item.quantity
        self.extra = dict(cart_item.extra)
        extra_rows = [(modifier, extra_row.data)
                      for modifier, extra_row in cart_item.extra_rows.items()]
        self.extra.update(rows=extra_rows)

    def save(self, *args, **kwargs):
        """
        Before saving the OrderItem object to the database, round the amounts to the given decimal places
        """
        self._unit_price = BaseOrder.round_amount(self._unit_price)
        self._line_total = BaseOrder.round_amount(self._line_total)
        super().save(*args, **kwargs)
Esempio n. 12
0
class BaseOrder(models.Model, metaclass=WorkflowMixinMetaclass):
    """
    An Order is the "in process" counterpart of the shopping cart, which freezes the state of the
    cart on the moment of purchase. It also holds stuff like the shipping and billing addresses,
    and keeps all the additional entities, as determined by the cart modifiers.
    """
    TRANSITION_TARGETS = {
        'new': _("New order without content"),
        'created': _("Order freshly created"),
        'payment_confirmed': _("Payment confirmed"),
        'payment_declined': _("Payment declined"),
    }
    decimalfield_kwargs = {
        'max_digits': 30,
        'decimal_places': 2,
    }
    decimal_exp = Decimal('.' + '0' * decimalfield_kwargs['decimal_places'])

    customer = deferred.ForeignKey(
        'BaseCustomer',
        on_delete=models.PROTECT,
        verbose_name=_("Customer"),
        related_name='orders',
    )

    status = FSMField(
        default='new',
        protected=True,
        verbose_name=_("Status"),
    )

    currency = models.CharField(
        max_length=7,
        editable=False,
        help_text=_("Currency in which this order was concluded"),
    )

    _subtotal = models.DecimalField(_("Subtotal"), **decimalfield_kwargs)

    _total = models.DecimalField(_("Total"), **decimalfield_kwargs)

    created_at = models.DateTimeField(
        _("Created at"),
        auto_now_add=True,
    )

    updated_at = models.DateTimeField(
        _("Updated at"),
        auto_now=True,
    )

    extra = JSONField(
        verbose_name=_("Extra fields"),
        help_text=
        _("Arbitrary information for this order object on the moment of purchase."
          ),
    )

    stored_request = JSONField(help_text=_(
        "Parts of the Request objects on the moment of purchase."), )

    objects = OrderManager()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = logging.getLogger('shop.order')

    def __str__(self):
        return self.get_number()

    def __repr__(self):
        return "<{}(pk={})>".format(self.__class__.__name__, self.pk)

    def get_or_assign_number(self):
        """
        Hook to get or to assign the order number. It shall be invoked, every time an Order
        object is created. If you prefer to use an order number which differs from the primary
        key, then override this method.
        """
        return self.get_number()

    def get_number(self):
        """
        Hook to get the order number.
        A class inheriting from Order may transform this into a string which is better readable.
        """
        return str(self.pk)

    def assign_secret(self):
        """
        Hook to assign a secret to authorize access on this Order object without authentication.
        """

    @property
    def secret(self):
        """
        Hook to return a secret if available.
        """

    @classmethod
    def resolve_number(cls, number):
        """
        Return a lookup pair used to filter down a queryset.
        It should revert the effect from the above method `get_number`.
        """
        return dict(pk=number)

    @property
    def subtotal(self):
        """
        The summed up amount for all ordered items excluding extra order lines.
        """
        return MoneyMaker(self.currency)(self._subtotal)

    @property
    def total(self):
        """
        The final total to charge for this order.
        """
        return MoneyMaker(self.currency)(self._total)

    @classmethod
    def round_amount(cls, amount):
        if amount.is_finite():
            return Decimal(amount).quantize(cls.decimal_exp)

    def get_absolute_url(self):
        """
        Returns the URL for the detail view of this order.
        """
        return urljoin(OrderModel.objects.get_summary_url(), self.get_number())

    @transaction.atomic
    @transition(field=status, source='new', target='created')
    def populate_from_cart(self, cart, request):
        """
        Populate the order object with the fields from the given cart.
        For each cart item a corresponding order item is created populating its fields and removing
        that cart item.

        Override this method, in case a customized cart has some fields which have to be transferred
        to the cart.
        """
        assert hasattr(cart, 'subtotal') and hasattr(cart, 'total'), \
            "Did you forget to invoke 'cart.update(request)' before populating from cart?"
        for cart_item in cart.items.all():
            cart_item.update(request)
            order_item = OrderItemModel(order=self)
            try:
                order_item.populate_from_cart_item(cart_item, request)
                order_item.save()
                cart_item.delete()
            except CartItemModel.DoesNotExist:
                pass
        self._subtotal = Decimal(cart.subtotal)
        self._total = Decimal(cart.total)
        self.extra = dict(cart.extra)
        self.extra.update(
            rows=[(modifier, extra_row.data)
                  for modifier, extra_row in cart.extra_rows.items()])
        self.save()

    @transaction.atomic
    def readd_to_cart(self, cart):
        """
        Re-add the items of this order back to the cart.
        """
        for order_item in self.items.all():
            extra = dict(order_item.extra)
            extra.pop('rows', None)
            extra.update(product_code=order_item.product_code)
            cart_item = order_item.product.is_in_cart(cart, **extra)
            if cart_item:
                cart_item.quantity = max(cart_item.quantity,
                                         order_item.quantity)
            else:
                cart_item = CartItemModel(cart=cart,
                                          product=order_item.product,
                                          product_code=order_item.product_code,
                                          quantity=order_item.quantity,
                                          extra=extra)
            cart_item.save()

    def save(self, with_notification=False, **kwargs):
        """
        :param with_notification: If ``True``, all notifications for the state of this Order object
        are executed.
        """
        from shop.transition import transition_change_notification

        auto_transition = self._auto_transitions.get(self.status)
        if callable(auto_transition):
            auto_transition(self)

        # round the total to the given decimal_places
        self._subtotal = BaseOrder.round_amount(self._subtotal)
        self._total = BaseOrder.round_amount(self._total)
        super().save(**kwargs)
        if with_notification:
            transition_change_notification(self)

    @cached_property
    def amount_paid(self):
        """
        The amount paid is the sum of related orderpayments
        """
        amount = self.orderpayment_set.aggregate(
            amount=Sum('amount'))['amount']
        if amount is None:
            amount = MoneyMaker(self.currency)()
        return amount

    @property
    def outstanding_amount(self):
        """
        Return the outstanding amount paid for this order
        """
        return self.total - self.amount_paid

    def is_fully_paid(self):
        return self.amount_paid >= self.total

    @transition(field='status',
                source='*',
                target='payment_confirmed',
                conditions=[is_fully_paid])
    def acknowledge_payment(self, by=None):
        """
        Change status to ``payment_confirmed``. This status code is known globally and can be used
        by all external plugins to check, if an Order object has been fully paid.
        """
        self.logger.info("Acknowledge payment by user %s", by)

    def cancelable(self):
        """
        A hook method to be overridden by mixin classes managing Order cancellations.

        :returns: ``True`` if the current Order is cancelable.
        """
        return False

    def refund_payment(self):
        """
        Hook to handle payment refunds.
        """

    def withdraw_from_delivery(self):
        """
        Hook to withdraw shipping order.
        """

    @classmethod
    def get_all_transitions(cls):
        """
        :returns: A generator over all transition objects for this Order model.
        """
        return cls.status.field.get_all_transitions(OrderModel)

    @classmethod
    def get_transition_name(cls, target):
        """
        :returns: The verbose name for a given transition target.
        """
        return cls._transition_targets.get(target, target)

    def status_name(self):
        """
        :returns: The verbose name for the current transition state.
        """
        return self._transition_targets.get(self.status, self.status)

    status_name.short_description = pgettext_lazy('order_models', "State")
Esempio n. 13
0
class Customer(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=False, primary_key=True
    )
    phone = models.CharField(max_length=20)
    birthdate = models.DateField(blank=True, null=True)
    image = models.ImageField(default="default.jpg", upload_to="profile_pics")
    last_access = models.DateTimeField(_("Last accessed"), default=timezone.now)

    recognized = models.IntegerField(choices=CUSTOMER_STATE, default=0)

    extra = JSONField(
        editable=False, verbose_name=_("Extra information about this customer")
    )

    objects = CustomerManager()

    def __str__(self):
        return self.get_username()

    def get_username(self):
        return self.user.get_username()

    def get_full_name(self):
        return self.user.get_full_name()

    @property
    def first_name(self):
        return self.user.first_name

    @first_name.setter
    def first_name(self, value):
        self.user.first_name = value

    @property
    def last_name(self):
        return self.user.last_name

    @last_name.setter
    def last_name(self, value):
        self.user.last_name = value

    @property
    def email(self):
        return self.user.email

    @email.setter
    def email(self, value):
        self.user.email = value

    @property
    def date_joined(self):
        return self.user.date_joined

    @property
    def last_login(self):
        return self.user.last_login

    @property
    def groups(self):
        return self.user.groups

    @property
    def is_anonymous(self):
        # print("\n1:", [CUSTOMER_STATE[0][0], CUSTOMER_STATE[1][0]])
        # print("2:", self.recognized)
        return self.recognized in [CUSTOMER_STATE[0][0], CUSTOMER_STATE[1][0]]

    @property
    def is_authenticated(self):
        return self.recognized == CUSTOMER_STATE[2][0]

    @property
    def is_recognized(self):
        """recognized
        Return True if the customer is associated with a User account.
        Unrecognized customers have accessed the shop, but did not register
        an account nor declared themselves as guests.
        """
        return self.recognized != CUSTOMER_STATE[0][0]

    @property
    def is_guest(self):
        """
        Return true if the customer isn't associated with valid User account, but declared
        himself as a guest, leaving their email address.
        """
        return self.recognized == CUSTOMER_STATE[1][0]

    def recognize_as_guest(self, request=None, commit=True):
        """
        Recognize the current customer as guest customer.
        """
        if self.recognized != CUSTOMER_STATE[1][0]:
            self.recognized = CUSTOMER_STATE[1][0]
            if commit:
                self.save(update_fields=["recognized"])
            customer_recognized.send(
                sender=self.__class__, customer=self, request=request
            )

    @property
    def is_registered(self):
        """
        Return true if the customer has registered himself.
        """
        return self.recognized == CUSTOMER_STATE[2][0]

    def recognize_as_registered(self, request=None, commit=True):
        """
        Recognize the current customer as registered customer.
        """
        if self.recognized != CUSTOMER_STATE[2][0]:
            self.recognized = CUSTOMER_STATE[2][0]
            if commit:
                self.save(update_fields=["recognized"])
            customer_recognized.send(
                sender=self.__class__, customer=self, request=request
            )

    @property
    def is_visitor(self):
        """
        Always False for instantiated Customer objects.
        """
        return False

    @property
    def is_expired(self):
        """
        Return True if the session of an unrecognized customer expired or is not decodable.
        Registered customers never expire.
        Guest customers only expire, if they failed fulfilling the purchase.
        """
        is_expired = False
        if self.recognized == CUSTOMER_STATE[0][0]:
            try:
                session_key = CustomerManager.decode_session_key(self.user.username)
                is_expired = not SessionStore.exists(session_key)
            except KeyError:
                msg = "Unable to decode username '{}' as session key"
                is_expired = True
        return is_expired

    def save(self, **kwargs):
        if "update_fields" not in kwargs:
            self.user.save(using=kwargs.get("using", DEFAULT_DB_ALIAS))
        super(Customer, self).save(**kwargs)

    def delete(self, *args, **kwargs):
        if self.user.is_active and self.recognized is CUSTOMER_STATE[0][0]:
            # invalid state of customer, keep the referred User
            super(Customer, self).delete(*args, **kwargs)
        else:
            # also delete self through cascading
            self.user.delete(*args, **kwargs)
Esempio n. 14
0
class BaseOrder(with_metaclass(WorkflowMixinMetaclass, models.Model)):
    """
    An Order is the "in process" counterpart of the shopping cart, which freezes the state of the
    cart on the moment of purchase. It also holds stuff like the shipping and billing addresses,
    and keeps all the additional entities, as determined by the cart modifiers.
    """
    TRANSITION_TARGETS = {
        'new': _("New order without content"),
        'created': _("Order freshly created"),
        'payment_confirmed': _("Payment confirmed"),
    }
    decimalfield_kwargs = {
        'max_digits': 30,
        'decimal_places': 2,
    }
    decimal_exp = Decimal('.' + '0' * decimalfield_kwargs['decimal_places'])

    customer = deferred.ForeignKey('BaseCustomer',
                                   verbose_name=_("Customer"),
                                   related_name='orders')
    status = FSMField(default='new', protected=True, verbose_name=_("Status"))
    currency = models.CharField(
        max_length=7,
        editable=False,
        help_text=_("Currency in which this order was concluded"))
    _subtotal = models.DecimalField(_("Subtotal"), **decimalfield_kwargs)
    _total = models.DecimalField(_("Total"), **decimalfield_kwargs)
    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
    updated_at = models.DateTimeField(_("Updated at"), auto_now=True)
    extra = JSONField(
        verbose_name=_("Extra fields"),
        help_text=
        _("Arbitrary information for this order object on the moment of purchase."
          ))
    stored_request = JSONField(
        help_text=_("Parts of the Request objects on the moment of purchase."))

    objects = OrderManager()

    class Meta:
        abstract = True

    def __str__(self):
        return self.get_number()

    def __repr__(self):
        return "<{}(pk={})>".format(self.__class__.__name__, self.pk)

    def get_or_assign_number(self):
        """
        Hook to get or to assign the order number. It shall be invoked, every time an Order
        object is created. If you prefer to use an order number which differs from the primary
        key, then override this method.
        """
        return self.get_number()

    def get_number(self):
        """
        Hook to get the order number.
        A class inheriting from Order may transform this into a string which is better readable.
        """
        return str(self.pk)

    @classmethod
    def resolve_number(cls, number):
        """
        Return a lookup pair used to filter down a queryset.
        It should revert the effect from the above method `get_number`.
        """
        return dict(pk=number)

    @cached_property
    def subtotal(self):
        """
        The summed up amount for all ordered items excluding extra order lines.
        """
        return MoneyMaker(self.currency)(self._subtotal)

    @cached_property
    def total(self):
        """
        The final total to charge for this order.
        """
        return MoneyMaker(self.currency)(self._total)

    @classmethod
    def round_amount(cls, amount):
        if amount.is_finite():
            return Decimal(amount).quantize(cls.decimal_exp)

    def get_absolute_url(self):
        """
        Returns the URL for the detail view of this order
        """
        return urljoin(OrderModel.objects.get_summary_url(), self.get_number())

    @transition(field=status, source='new', target='created')
    def populate_from_cart(self, cart, request):
        """
        Populate the order object with the fields from the given cart. Override this method,
        in case a customized cart has some fields which have to be transfered to the cart.
        """
        self._subtotal = Decimal(cart.subtotal)
        self._total = Decimal(cart.total)
        self.extra = dict(cart.extra)
        self.extra.update(
            rows=[(modifier, extra_row.data)
                  for modifier, extra_row in cart.extra_rows.items()])

    @transaction.atomic
    def readd_to_cart(self, cart):
        """
        Re-add the items of this order back to the cart.
        """
        for order_item in self.items.all():
            extra = dict(order_item.extra)
            extra.pop('rows', None)
            cart_item = order_item.product.is_in_cart(cart, **extra)
            if cart_item:
                cart_item.quantity = max(cart_item.quantity,
                                         order_item.quantity)
            else:
                cart_item = CartItemModel(cart=cart,
                                          product=order_item.product,
                                          quantity=order_item.quantity,
                                          extra=extra)
            cart_item.save()

    def save(self, **kwargs):
        """
        The status of an Order object my change, if auto transistions are specified.
        """
        auto_transition = self._auto_transitions.get(self.status)
        if callable(auto_transition):
            auto_transition(self)

        # round the total to the given decimal_places
        self._subtotal = BaseOrder.round_amount(self._subtotal)
        self._total = BaseOrder.round_amount(self._total)
        super(BaseOrder, self).save(**kwargs)

    @cached_property
    def amount_paid(self):
        """
        The amount paid is the sum of related orderpayments
        """
        amount = self.orderpayment_set.aggregate(
            amount=Sum('amount'))['amount']
        if amount is None:
            amount = MoneyMaker(self.currency)()
        return amount

    @property
    def outstanding_amount(self):
        """
        Return the outstanding amount paid for this order
        """
        return self.total - self.amount_paid

    def is_fully_paid(self):
        return self.amount_paid >= self.total

    @transition(field='status',
                source='*',
                target='payment_confirmed',
                conditions=[is_fully_paid])
    def acknowledge_payment(self, by=None):
        """
        Change status to `payment_confirmed`. This status code is known globally and can be used
        by all external plugins to check, if an Order object has been fully paid.
        """

    @classmethod
    def get_transition_name(cls, target):
        """Return the human readable name for a given transition target"""
        return cls._transition_targets.get(target, target)

    def status_name(self):
        """Return the human readable name for the current transition state"""
        return self._transition_targets.get(self.status, self.status)

    status_name.short_description = pgettext_lazy('order_models', "State")
Esempio n. 15
0
class BaseCartItem(models.Model, metaclass=deferred.ForeignKeyBuilder):
    """
    This is a holder for the quantity of items in the cart and, obviously, a
    pointer to the actual Product being purchased
    """
    cart = deferred.ForeignKey(
        'BaseCart',
        on_delete=models.CASCADE,
        related_name='items',
    )

    product = deferred.ForeignKey(
        BaseProduct,
        on_delete=models.CASCADE,
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Product code of added item."),
    )

    updated_at = models.DateTimeField(
        _("Updated at"),
        auto_now=True,
    )

    extra = JSONField(
        verbose_name=_("Arbitrary information for this cart item"))

    objects = CartItemManager()

    class Meta:
        abstract = True
        verbose_name = _("Cart item")
        verbose_name_plural = _("Cart items")

    @classmethod
    def check(cls, **kwargs):
        errors = super().check(**kwargs)
        allowed_types = [
            'IntegerField', 'SmallIntegerField', 'PositiveIntegerField',
            'PositiveSmallIntegerField', 'DecimalField', 'FloatField'
        ]
        for field in cls._meta.fields:
            if field.attname == 'quantity':
                if field.get_internal_type() not in allowed_types:
                    msg = "Class `{}.quantity` must be of one of the types: {}."
                    errors.append(
                        checks.Error(msg.format(cls.__name__, allowed_types)))
                break
        else:
            msg = "Class `{}` must implement a field named `quantity`."
            errors.append(checks.Error(msg.format(cls.__name__)))
        return errors

    def __init__(self, *args, **kwargs):
        # reduce the given fields to what the model actually can consume
        all_field_names = [
            field.name for field in self._meta.get_fields(include_parents=True)
        ]
        model_kwargs = {
            k: v
            for k, v in kwargs.items() if k in all_field_names
        }
        super().__init__(*args, **model_kwargs)
        self.extra_rows = OrderedDict()
        self._dirty = True

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        self.cart.save(update_fields=['updated_at'])
        self._dirty = True

    def update(self, request):
        """
        Loop over all registered cart modifier, change the price per cart item and optionally add
        some extra rows.
        """
        if not self._dirty:
            return
        self.refresh_from_db()
        self.extra_rows = OrderedDict()  # reset the dictionary
        for modifier in cart_modifiers_pool.get_all_modifiers():
            modifier.process_cart_item(self, request)
        self._dirty = False
Esempio n. 16
0
class BaseOrderItem(with_metaclass(deferred.ForeignKeyBuilder, models.Model)):
    """
    An item for an order.
    """
    order = deferred.ForeignKey(BaseOrder,
                                related_name='items',
                                verbose_name=_("Order"))
    product_name = models.CharField(
        _("Product name"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Product name at the moment of purchase."))
    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Product code at the moment of purchase."))
    product = deferred.ForeignKey(BaseProduct,
                                  null=True,
                                  blank=True,
                                  on_delete=models.SET_NULL,
                                  verbose_name=_("Product"))
    _unit_price = models.DecimalField(
        _("Unit price"),
        null=True,  # may be NaN
        help_text=_("Products unit price at the moment of purchase."),
        **BaseOrder.decimalfield_kwargs)
    _line_total = models.DecimalField(
        _("Line Total"),
        null=True,  # may be NaN
        help_text=_("Line total on the invoice at the moment of purchase."),
        **BaseOrder.decimalfield_kwargs)
    extra = JSONField(verbose_name=_("Extra fields"),
                      help_text=_("Arbitrary information for this order item"))

    class Meta:
        abstract = True
        verbose_name = _("Order item")
        verbose_name_plural = _("Order items")

    def __str__(self):
        return self.product_name

    @classmethod
    def perform_model_checks(cls):
        try:
            cart_field = [
                f for f in CartItemModel._meta.fields
                if f.attname == 'quantity'
            ][0]
            order_field = [
                f for f in cls._meta.fields if f.attname == 'quantity'
            ][0]
            if order_field.get_internal_type() != cart_field.get_internal_type(
            ):
                msg = "Field `{}.quantity` must be of one same type `{}.quantity`."
                raise ImproperlyConfigured(
                    msg.format(cls.__name__, CartItemModel.__name__))
        except IndexError:
            msg = "Class `{}` must implement a field named `quantity`."
            raise ImproperlyConfigured(msg.format(cls.__name__))

    @cached_property
    def unit_price(self):
        return MoneyMaker(self.order.currency)(self._unit_price)

    @cached_property
    def line_total(self):
        return MoneyMaker(self.order.currency)(self._line_total)

    def populate_from_cart_item(self, cart_item, request):
        """
        From a given cart item, populate the current order item.
        If the operation was successful, the given item shall be removed from the cart.
        If a CartItem.DoesNotExist exception is raised, discard the order item.
        """
        if cart_item.quantity == 0:
            raise CartItemModel.DoesNotExist("Cart Item is on the Wish List")
        self.product = cart_item.product
        # for historical integrity, store the product's name and price at the moment of purchase
        self.product_name = cart_item.product.product_name
        self.product_code = cart_item.product_code
        self._unit_price = Decimal(cart_item.product.get_price(request))
        self._line_total = Decimal(cart_item.line_total)
        self.quantity = cart_item.quantity
        self.extra = dict(cart_item.extra)
        extra_rows = [(modifier, extra_row.data)
                      for modifier, extra_row in cart_item.extra_rows.items()]
        self.extra.update(rows=extra_rows)

    def save(self, *args, **kwargs):
        """
        Before saving the OrderItem object to the database, round the amounts to the given decimal places
        """
        self._unit_price = BaseOrder.round_amount(self._unit_price)
        self._line_total = BaseOrder.round_amount(self._line_total)
        super(BaseOrderItem, self).save(*args, **kwargs)
Esempio n. 17
0
class Order(models.Model):
    """
    An Order is the "in process" counterpart of the shopping cart, which freezes the state of the
    cart on the moment of purchase. It also holds stuff like the shipping and billing addresses,
    and keeps all the additional entities, as determined by the cart modifiers.
    """

    TRANSITION_TARGETS = {
        "new": _("New order without content"),
        "created": _("Order freshly created"),
        "payment_confirmed": _("Payment confirmed"),
        "payment_declined": _("Payment declined"),
    }
    decimalfield_kwargs = {"max_digits": 30, "decimal_places": 2}
    decimal_exp = Decimal("." + "0" * decimalfield_kwargs["decimal_places"])

    customer = models.ForeignKey(
        "Customer",
        verbose_name=_("Customer"),
        related_name="orders",
        on_delete=models.PROTECT,
    )

    status = FSMField(default="new", protected=True, verbose_name=_("Status"))

    currency = models.CharField(
        max_length=7,
        editable=False,
        help_text=_("Currency in which this order was concluded"),
    )

    _subtotal = models.DecimalField(_("Subtotal"), **decimalfield_kwargs)

    _total = models.DecimalField(_("Total"), **decimalfield_kwargs)

    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)

    updated_at = models.DateTimeField(_("Updated at"), auto_now=True)

    extra = JSONField(
        verbose_name=_("Extra fields"),
        help_text=
        _("Arbitrary information for this order object on the moment of purchase."
          ),
    )

    stored_request = JSONField(
        help_text=_("Parts of the Request objects on the moment of purchase."))

    objects = OrderManager()

    def __init__(self, *args, **kwargs):
        super(Order, self).__init__(*args, **kwargs)
        self.logger = logging.getLogger("shop.order")

    def __str__(self):
        return self.get_number()

    def __repr__(self):
        return "<{}(pk={})>".format(self.__class__.__name__, self.pk)

    def get_number(self):
        """
        Hook to get the order number.
        A class inheriting from Order may transform this into a string which is better readable.
        """
        return str(self.pk)

    @classmethod
    def round_amount(cls, amount):
        if amount.is_finite():
            return Decimal(amount).quantize(cls.decimal_exp)