Пример #1
0
class Item(Ordered, Dated, db.EmbeddedDocument):
    product = db.ReferenceField(Content)
    reference = db.GenericReferenceField()  # customized product
    """
    Must implement all the BaseProduct methods/ its optional
    if None, "product" will be considered
    """
    uid = db.StringField()
    title = db.StringField(required=True, max_length=255)
    description = db.StringField(required=True)
    link = db.StringField()
    quantity = db.FloatField(default=1)
    unity_value = db.FloatField(required=True)
    total_value = db.FloatField()
    weight = db.FloatField()
    dimensions = db.StringField()
    extra_value = db.FloatField()
    allowed_to_set = db.ListField(db.StringField(), default=['quantity'])
    pipeline = db.ListField(db.StringField(), default=[])

    def set_status(self, status, *args, **kwargs):
        kwargs['item'] = self
        if self.reference and hasattr(self.reference, 'set_status'):
            self.reference.set_status(status, *args, **kwargs)
        if self.product and hasattr(self.product, 'set_status'):
            self.product.set_status(status, *args, **kwargs)

    def get_main_image_url(self, thumb=False, default=None):
        try:
            return self.product.get_main_image_url(thumb, default)
        except:
            return None

    @classmethod
    def normalize(cls, kwargs):
        new = {}
        for k, v in kwargs.items():
            field = cls._fields.get(k)
            if not field:
                continue
            new[k] = field.to_python(v)
        return new

    def __unicode__(self):
        return u"{i.title} - {i.total_value}".format(i=self)

    def get_uid(self):
        try:
            return self.product.get_uid()
        except:
            return self.uid

    @property
    def unity_plus_extra(self):
        return float(self.unity_value or 0) + float(self.extra_value or 0)

    @property
    def total(self):
        self.clean()
        self.total_value = self.unity_plus_extra * float(self.quantity or 1)
        return self.total_value

    def clean(self):
        mapping = [
            ('title', 'get_title'),
            ('description', 'get_description'),
            ('link', 'get_absolute_url'),
            ('unity_value', 'get_unity_value'),
            ('weight', 'get_weight'),
            ('dimensions', 'get_dimensions'),
            ('extra_value', 'get_extra_value'),
            ('uid', 'get_uid'),
        ]

        references = [self.reference, self.product]

        for ref in references:
            if not ref:
                continue
            for attr, method in mapping:
                current = getattr(self, attr, None)
                if current is not None:
                    continue
                setattr(self, attr, getattr(ref, method, lambda: None)())
Пример #2
0
class ContentProxy(db.DynamicDocument):
    content = db.GenericReferenceField(required=True, unique=True)

    def __unicode__(self):
        return self.content.title
Пример #3
0
class Cart(Publishable, db.DynamicDocument):
    STATUS = (
        ("pending", _l("Pending")),  # not checked out
        ("checked_out", _l("Checked out")),  # not confirmed (payment)
        ("analysing", _l("Analysing")),  # Analysing payment
        ("confirmed", _l("Confirmed")),  # Payment confirmed
        ("completed", _l("Completed")),  # Payment completed (money released)
        ("refunding", _l("Refunding")),  # Buyer asks refund
        ("refunded", _l("Refunded")),  # Money refunded to buyer
        ("cancelled", _l("Cancelled")),  # Cancelled without processing
        ("abandoned", _l("Abandoned")),  # Long time no update
    )
    reference = db.GenericReferenceField()
    """reference must implement set_status(**kwargs) method
    arguments: status(str), value(float), date, uid(str), msg(str)
    and extra(dict).
    Also reference must implement get_uid() which will return
    the unique identifier for this transaction"""

    belongs_to = db.ReferenceField(
        'User',
        # default=get_current_user,
        reverse_delete_rule=db.NULLIFY)
    items = db.ListField(db.EmbeddedDocumentField(Item))
    payment = db.ListField(db.EmbeddedDocumentField(Payment))
    status = db.StringField(choices=STATUS, default='pending')
    total = db.FloatField(default=0)
    extra_costs = db.DictField(default=lambda: {})
    sender_data = db.DictField(default=lambda: {})
    shipping_data = db.DictField(default=lambda: {})
    shipping_cost = db.FloatField(default=0)
    tax = db.FloatField(default=0)
    processor = db.ReferenceField(Processor,
                                  default=Processor.get_default_processor,
                                  reverse_delete_rule=db.NULLIFY)
    reference_code = db.StringField()  # Reference code for filtering
    checkout_code = db.StringField()  # The UID for transaction checkout
    transaction_code = db.StringField()  # The UID for transaction
    requires_login = db.BooleanField(default=True)
    continue_shopping_url = db.StringField(
        default=lambda: current_app.config.get('CART_CONTINUE_SHOPPING_URL',
                                               '/'))
    pipeline = db.ListField(db.StringField(), default=[])
    log = db.ListField(db.StringField(), default=[])
    config = db.DictField(default=lambda: {})

    search_helper = db.StringField()

    meta = {'ordering': ['-created_at']}

    def send_response(self, response, identifier):
        if self.reference and hasattr(self.reference, 'get_response'):
            self.reference.get_response(response, identifier)

        for item in self.items:
            if hasattr(item, 'get_response'):
                item.get_response(response, identifier)

    def set_tax(self, tax, save=False):
        """
        set tax and send to references
        """
        try:
            tax = float(tax)
            self.tax = tax
            self.set_reference_tax(tax)
        except Exception as e:
            self.addlog("impossible to set tax: %s" % str(e))

    def set_status(self, status, save=False):
        """
        THis method will be called by the processor
        which will pass a valid status as in STATUS
        so, this method will dispatch the STATUS to
        all the items and also the 'reference' if set
        """
        if self.status != status:
            self.status = status

        self.set_reference_statuses(status)

        if save:
            self.save()

    def set_reference_statuses(self, status):
        if self.reference and hasattr(self.reference, 'set_status'):
            self.reference.set_status(status, cart=self)

        for item in self.items:
            item.set_status(status, cart=self)

    def set_reference_tax(self, tax):
        if self.reference and hasattr(self.reference, 'set_tax'):
            self.reference.set_tax(tax)

        for item in self.items:
            if hasattr(item, 'set_tax'):
                item.set_tax(tax)

    def addlog(self, msg, save=True):
        try:
            self.log.append(u"{0},{1}".format(datetime.datetime.now(), msg))
            logger.debug(msg)
            save and self.save()
        except UnicodeDecodeError as e:
            logger.info(msg)
            logger.error(str(e))

    @property
    def uid(self):
        return self.get_uid()

    def get_uid(self):
        try:
            return self.reference.get_uid() or str(self.id)
        except Exception:
            self.addlog("Using self.id as reference", save=False)
            return str(self.id)

    def __unicode__(self):
        return u"{o.uid} - {o.processor.identifier}".format(o=self)

    def get_extra_costs(self):
        if self.extra_costs:
            return sum(self.extra_costs.values())

    @classmethod
    def get_cart(cls, no_dereference=False, save=True):
        """
        get or create a new cart related to the session
        if there is a current logged in user it will be set
        else it will be set during the checkout.
        """
        session.permanent = current_app.config.get("CART_PERMANENT_SESSION",
                                                   True)
        try:
            cart = cls.objects(id=session.get('cart_id'), status='pending')

            if not cart:
                raise cls.DoesNotExist('A pending cart not found')

            if no_dereference:
                cart = cart.no_dereference()

            cart = cart.first()

            save and cart.save()

        except (cls.DoesNotExist, db.ValidationError):
            cart = cls(status="pending")
            cart.save()
            session['cart_id'] = str(cart.id)
            session.pop('cart_pipeline_index', None)
            session.pop('cart_pipeline_args', None)

        return cart

    def assign(self):
        self.belongs_to = self.belongs_to or get_current_user()

    def save(self, *args, **kwargs):
        self.total = sum([item.total for item in self.items])
        self.assign()
        self.reference_code = self.get_uid()
        self.search_helper = self.get_search_helper()
        if not self.id:
            self.published = True
        super(Cart, self).save(*args, **kwargs)
        self.set_reference_statuses(self.status)

    def get_search_helper(self):
        if not self.belongs_to:
            return ""
        user = self.belongs_to
        return " ".join([user.name or "", user.email or ""])

    def get_item(self, uid):
        # MongoEngine/mongoengine#503
        return self.items.get(uid=uid)

    def set_item(self, **kwargs):
        if 'product' in kwargs:
            if not isinstance(kwargs['product'], Content):
                try:
                    kwargs['product'] = Content.objects.get(
                        id=kwargs['product'])
                except Content.DoesNotExist:
                    kwargs['product'] = None

        uid = kwargs.get(
            'uid',
            kwargs['product'].get_uid() if kwargs.get('product') else None)

        if not uid:
            self.addlog("Cannot add item without an uid %s" % kwargs)
            return

        item = self.get_item(uid)

        kwargs = Item.normalize(kwargs)

        if not item:
            # items should only be added if there is a product (for safety)
            if not kwargs.get('product'):
                self.addlog("there is no product to add item")
                return
            allowed = ['product', 'quantity']
            item = self.items.create(
                **{k: v
                   for k, v in kwargs.items() if k in allowed})
            self.addlog("New item created %s" % item, save=False)
        else:
            # update only allowed attributes
            item = self.items.update(
                {k: v
                 for k, v in kwargs.items() if k in item.allowed_to_set},
                uid=item.uid)
            self.addlog("Item updated %s" % item, save=False)

            if int(kwargs.get('quantity', "1")) == 0:
                self.addlog("quantity is 0, removed %s" % kwargs, save=False)
                self.remove_item(**kwargs)

        self.save()
        self.reload()
        return item

    def remove_item(self, **kwargs):
        deleted = self.items.delete(**kwargs)
        if self.reference and hasattr(self.reference, 'remove_item'):
            self.reference.remove_item(**kwargs)
        return deleted

    def checkout(self, processor=None, *args, **kwargs):
        self.set_processor(processor)
        processor_instance = self.processor.get_instance(self, *args, **kwargs)
        if processor_instance.validate():
            response = processor_instance.process()
            self.status = 'checked_out'
            self.save()
            session.pop('cart_id', None)
            return response
        else:
            self.addlog("Cart did not validate")
            raise Exception("Cart did not validate")  # todo: specialize this

    def get_items_pipeline(self):
        if not self.items:
            return []

        return reduce(lambda x, y: x + y,
                      [item.pipeline for item in self.items])

    def build_pipeline(self):
        items = ['quokka.modules.cart.pipelines:StartPipeline']
        items.extend(current_app.config.get('CART_PIPELINE', []))
        items.extend(self.get_items_pipeline())
        items.extend(self.pipeline)
        items.extend(self.processor and self.processor.pipeline or [])
        return items

    def process_pipeline(self):
        if not self.items:
            return render_template('cart/empty_cart.html',
                                   url=self.continue_shopping_url)

        pipelines = self.build_pipeline()
        index = session.get('cart_pipeline_index', 0)
        pipeline = import_string(pipelines[index])
        return pipeline(self, pipelines, index)._preprocess()

    def set_processor(self, processor=None):
        if not self.processor:
            self.processor = Processor.get_default_processor()
            self.save()

        if not processor:
            return

        if isinstance(processor, Processor):
            self.processor = processor
            self.save()
            return

        try:
            self.processor = Processor.objects.get(id=processor)
        except:
            self.processor = Processor.objects.get(identifier=processor)

        self.save()

    def get_available_processors(self):
        return Processor.objects(published=True)