Exemple #1
0
class DocumentView(MediaView):
    """
    Edit CMS documents.
    """
    namespace = 'cubane.cms.documents'
    context = {'is_image': False}
    exclude_columns = ['size', 'image_size']

    PATTERNS = [
        view_url(r'download/', 'download', name='download'),
        view_url(r'preview/', 'preview', name='preview')
    ]

    def before(self, request, handler):
        request.is_image = False

    def _get_objects(self, request):
        return self.model.objects.select_related('parent').filter(
            is_image=False)

    def preview(self, request):
        return {'url': request.GET.get('url')}
Exemple #2
0
    def __init__(self, *args, **kwargs):
        self.patterns = copy.copy(self.PATTERNS)
        self.listing_actions = copy.copy(self.LISTING_ACTIONS)
        self.shortcut_actions = copy.copy(self.SHORTCUT_ACTIONS)

        # auto fit
        self.exclude_columns = []
        if not settings.IMAGE_FITTING_ENABLED or not settings.IMAGE_FITTING_SHAPES:
            self.exclude_columns.append('auto_fit')

        # provide media sharing if CMS is used
        if 'cubane.cms' in settings.INSTALLED_APPS:
            self.patterns.append(view_url(r'share/', 'share', name='share'))
            self.listing_actions.append(('[/Share]', 'share', 'single'))
            self.shortcut_actions.append('share')
        else:
            self.exclude_columns.append('share_enabled')

        super(MediaView, self).__init__(*args, **kwargs)
Exemple #3
0
    def __init__(self, *args, **kwargs):
        self.patterns = []
        self.listing_actions = []
        self.shortcut_actions = []

        # view order (previous order screen)
        self.patterns.append(view_url(r'view/$', 'view', name='view'))
        self.listing_actions.append(('View', 'view', 'single'))
        self.shortcut_actions.append('view')

        # support for pre-auth payments (approval)
        if settings.SHOP_PREAUTH:
            self.patterns.extend([
                view_url(r'approve/$', 'approve', name='approve'),
                view_url(r'reject/$', 'reject', name='reject'),
                view_url(r'cancel/$', 'cancel', name='cancel')
            ])
            self.listing_actions.extend([('[Approve]', 'approve', 'single'),
                                         ('[Reject]', 'reject', 'single'),
                                         ('[Cancel]', 'cancel', 'single')])
            self.shortcut_actions.extend([
                'approve',
                'reject',
                'cancel',
            ])
        else:
            self.exclude_columns = ['approval_status']

        self.patterns.extend([
            view_url(r'basket-editor/', 'basket_editor', name='basket_editor'),
            view_url(r'basket-editor-search/',
                     'basket_editor_search',
                     name='basket_editor_search'),
            view_url(r'basket-editor-add-to-basket/',
                     'basket_editor_add_to_basket',
                     name='basket_editor_add_to_basket'),
        ])

        super(OrderView, self).__init__(*args, **kwargs)
Exemple #4
0
class ProductView(ModelView):
    """
    Editing categories (tree)
    """
    template_path = 'cubane/ishop/merchant/products/'
    model = get_product_model()

    def __init__(self, namespace, with_folders):
        self.namespace = namespace
        self.with_folders = with_folders

        if with_folders:
            self.folder_model = get_category_model()

        self.sortable = self.with_folders

        # multiple categories
        if settings.SHOP_MULTIPLE_CATEGORIES:
            self.exclude_columns = ['category']
            self.multiple_folders = True
        else:
            self.exclude_columns = ['categories_display']
            self.multiple_folders = False

        super(ProductView, self).__init__()

    patterns = [
        view_url(r'varieties/', 'varieties', name='varieties'),
        view_url(r'varieties/(?P<product_id>\d+)/edit/(?P<variety_id>\d+)/',
                 'varieties_edit',
                 name='varieties.edit'),
        view_url(r'varieties/(?P<product_id>\d+)/delete/(?P<variety_id>\d+)',
                 'varieties_delete',
                 name='varieties.delete'),
        view_url(r'sku/', 'sku', name='sku'),
        view_url(r'delivery/', 'delivery', name='delivery'),
        view_url(r'google-products-export/',
                 'google_products',
                 name='google_products'),
    ]

    listing_actions = [('[SKUs]', 'sku', 'single'),
                       ('[Varieties]', 'varieties', 'single'),
                       ('[Delivery]', 'delivery', 'single'),
                       ('Export To Google', 'google_products', 'any')]

    shortcut_actions = ['sku', 'varieties', 'delivery']

    def _get_objects(self, request):
        if settings.SHOP_MULTIPLE_CATEGORIES:
            # multiple categories
            return self.model.objects.prefetch_related(
                Prefetch('categories',
                         queryset=ProductCategory.objects.select_related(
                             'category').order_by('seq'))).distinct()
        else:
            # single category
            return self.model.objects.select_related('category').all()

    def _get_folders(self, request, parent):
        folders = self.folder_model.objects.all()

        if parent:
            folders = folders.filter(parent=parent)

        return folders

    def _folder_filter(self, request, objects, category_pks):
        """
        Filter given object queryset by the given folder primary key.
        """
        if category_pks:
            q = Q()
            if settings.SHOP_MULTIPLE_CATEGORIES:
                # multiple categories
                for pk in category_pks:
                    q |= Q(categories__id=pk) | \
                         Q(categories__parent_id=pk) | \
                         Q(categories__parent__parent_id=pk) | \
                         Q(categories__parent__parent__parent_id=pk) | \
                         Q(categories__parent__parent__parent__parent_id=pk) | \
                         Q(categories__parent__parent__parent__parent__parent_id=pk)
            else:
                # single category
                for pk in category_pks:
                    q |= Q(category_id=pk) | \
                         Q(category__parent_id=pk) | \
                         Q(category__parent__parent_id=pk) | \
                         Q(category__parent__parent__parent_id=pk) | \
                         Q(category__parent__parent__parent__parent_id=pk) | \
                         Q(category__parent__parent__parent__parent__parent_id=pk)

            # apply filter
            objects = objects.filter(q)
        return objects

    def _get_folder_assignment_name(self):
        """
        Return the name of the field that is used to assign a folder to.
        """
        if settings.SHOP_MULTIPLE_CATEGORIES:
            return 'categories'
        else:
            return 'category'

    def form_initial(self, request, initial, instance, edit):
        """
        Setup gallery images (initial form data)
        """
        initial['_gallery_images'] = load_media_gallery(
            instance.gallery_images)
        initial['_related_products_collection'] = RelatedModelCollection.load(
            instance, RelatedProducts)

        if settings.SHOP_MULTIPLE_CATEGORIES:
            initial['categories'] = RelatedModelCollection.load(
                instance, ProductCategory, sortable=False)

    def bulk_form_initial(self, request, initial, instance, edit):
        if settings.SHOP_MULTIPLE_CATEGORIES:
            initial['categories'] = RelatedModelCollection.load(
                instance, ProductCategory, sortable=False)

    def before_save(self, request, cleaned_data, instance, edit):
        """
        Maintain SKU based on barcode.
        """
        if request.settings.sku_is_barcode:
            instance.sku = cleaned_data.get('barcode')

    def after_save(self, request, d, instance, edit):
        """
        Save gallery items (in seq.)
        """
        save_media_gallery(request, instance, d.get('_gallery_images'))
        RelatedModelCollection.save(request, instance,
                                    d.get('_related_products_collection'),
                                    RelatedProducts)

        if settings.SHOP_MULTIPLE_CATEGORIES:
            RelatedModelCollection.save(request,
                                        instance,
                                        d.get('categories'),
                                        ProductCategory,
                                        allow_duplicates=False,
                                        sortable=False)

    def after_bulk_save(self, request, d, instance, edit):
        if settings.SHOP_MULTIPLE_CATEGORIES:
            RelatedModelCollection.save(request,
                                        instance,
                                        d.get('categories'),
                                        ProductCategory,
                                        allow_duplicates=False,
                                        sortable=False)

    def varieties(self, request):
        product_id = request.GET.get('pk')
        product = get_object_or_404(get_product_model(), pk=product_id)

        # load variety options
        variety_options = [
            (a.variety_option.variety, a.variety_option)
            for a in VarietyAssignment.objects.select_related(
                'variety_option', 'variety_option__variety').filter(
                    product=product)
        ]
        assigned_variety_ids = [variety.pk for variety, _ in variety_options]

        # load all varieties and split them into assigned and unassigned
        varieties = Variety.objects.prefetch_related('options').exclude(
            options=None).order_by('title')
        if product.sku_enabled:
            varieties = varieties.filter(sku=False)

        # split into assigned and unassigned
        varieties = list(varieties)
        assigned = filter(lambda v: v.id in assigned_variety_ids, varieties)
        unassigned = filter(lambda v: v.id not in assigned_variety_ids,
                            varieties)

        # inject list of assigned variety options for all assigned varieties
        for v in assigned:
            # collect assigned options
            assigned_options = []
            for variety, option in variety_options:
                if v.pk == variety.pk:
                    assigned_options.append(option)

            # sort by title and construct display text
            assigned_options = sorted(assigned_options, key=lambda o: o.title)
            v.assigned_options_display = ', '.join(
                option.title.strip() for option in assigned_options[:5]) + (
                    ', ...' if len(assigned_options) > 5 else '')

        return {
            'product': product,
            'assigned': assigned,
            'unassigned': unassigned,
            'ok_url': self._get_url(request, 'index')
        }

    def varieties_edit(self, request, product_id, variety_id):
        product = get_object_or_404(get_product_model(), pk=product_id)
        variety = get_object_or_404(Variety, pk=variety_id)
        options = list(variety.options.order_by('seq', 'id'))
        assignments = VarietyAssignment.objects.select_related(
            'variety_option').filter(product=product,
                                     variety_option__variety=variety)
        assignment_list = list(assignments)

        # dataset based on available options
        initial = [{
            'option_id': option.id,
            'title': option.title,
            'enabled': False,
            'offset_type': option.default_offset_type,
            'offset_value': option.default_offset_value,
            'text_label': option.text_label,
            'seq': option.seq,
            'option_enabled': option.enabled
        } for option in options]

        # update base dataset based on available assignments...
        for initial_option in initial:
            for assignment in assignment_list:
                if initial_option['option_id'] == assignment.variety_option.id:
                    initial_option['enabled'] = True
                    initial_option['option_enabled'] = True
                    initial_option['offset_type'] = assignment.offset_type
                    initial_option['offset_value'] = assignment.offset_value

        # remove options that are not currently assigned but disabled...
        initial = filter(lambda option: option.get('option_enabled'), initial)

        # sort by enabled state, then seq if we have a lot of varieties
        if len(initial) > 15:
            initial = sorted(initial,
                             key=lambda x: (-x.get('enabled', x.get('seq'))))

        # determine form class
        if variety.is_attribute:
            form_class = VarietyAttributeAssignmentFormset
        else:
            form_class = VarietyAssignmentFormset

        # create form
        if request.method == 'POST':
            formset = form_class(request.POST)
        else:
            formset = form_class(initial=initial)

        # validation
        if formset.is_valid():
            # delete existing assignments
            for assignment in assignments:
                request.changelog.delete(assignment)
                assignment.delete()

            # create new assignments
            for form in formset.forms:
                d = form.cleaned_data
                if d.get('enabled') == True:
                    for option in options:
                        if option.id == d.get('option_id'):
                            assignment = VarietyAssignment()
                            assignment.variety_option = option
                            assignment.product = product

                            if not variety.is_attribute:
                                assignment.offset_type = d.get('offset_type')
                                assignment.offset_value = d.get('offset_value')

                            assignment.save()
                            request.changelog.create(assignment)
                            break

            request.changelog.commit(
                'Variety Options for <em>%s</em> for product <em>%s</em> updated.'
                % (variety.title, product.title),
                product,
                flash=True)

            active_tab = request.POST.get('cubane_save_and_continue', '0')
            if not active_tab == '0':
                return self._redirect(request,
                                      'varieties.edit',
                                      args=[product.id, variety.id])
            else:
                return self._redirect(request, 'varieties', product)

        return {
            'product': product,
            'variety': variety,
            'form': formset,
        }

    @view(require_POST)
    def varieties_delete(self, request, product_id, variety_id):
        product = get_object_or_404(get_product_model(), pk=product_id)
        variety = get_object_or_404(Variety, pk=variety_id)
        assignments = VarietyAssignment.objects.filter(
            product=product, variety_option__variety=variety)

        # delete assignments
        for assignment in assignments:
            request.changelog.delete(assignment)
            assignment.delete()

        request.changelog.commit(
            'Variety <em>%s</em> removed.' % variety.title, product)
        return to_json_response({
            'success': True,
        })

    def sku(self, request):
        # get product
        product_id = request.GET.get('pk')
        product = get_object_or_404(get_product_model(), pk=product_id)

        # get varieties
        _varieties = Variety.objects.prefetch_related(
            Prefetch('options',
                     queryset=VarietyOption.objects.order_by('title'))
        ).filter(sku=True).exclude(options=None).exclude(
            style=Variety.STYLE_ATTRIBUTE).order_by('title').distinct()
        skus = ProductSKU.objects.filter(product=product)
        assigned_option_ids = [
            a.variety_option.id
            for a in VarietyAssignment.objects.select_related('variety_option')
            .filter(product=product, variety_option__variety__sku=True)
        ]

        # initial dataset currently present
        initial = {}
        for sku in skus:
            initial[sku.pk] = sku
            initial[sku.pk].errors = []

        # determine barcode system
        cms_settings = get_cms_settings()
        barcode_system = cms_settings.get_barcode_system(product)

        # create template form
        form_template = ProductSKUForm()
        form_template.configure(request, barcode_system)

        def has_var(prefix, name):
            return 'f-%s-%s' % (prefix, name) in request.POST

        def get_var(prefix, name, default=None):
            return request.POST.get('f-%s-%s' % (prefix, name), default)

        def get_int_var(prefix, name, default=None):
            return parse_int(get_var(prefix, name), default)

        # construct list of variety option names
        varieties = []
        variety_index = {}
        for variety in _varieties:
            variety_index[variety.id] = {
                'id': variety.id,
                'title': variety.title,
                'sku': variety.sku,
                'options': {}
            }

            item = {
                'id': variety.id,
                'title': variety.title,
                'sku': variety.sku,
                'options': [],
                'n_assigned_options': 0
            }
            for option in variety.options.all():
                variety_index[variety.id].get('options')[option.id] = {
                    'id': option.id,
                    'title': option.title,
                    'fullTitle':
                    '%s: <em>%s</em>' % (variety.title, option.title)
                }
                item.get('options').append({
                    'id':
                    option.id,
                    'title':
                    option.title,
                    'assigned':
                    option.id in assigned_option_ids
                })
                if option.pk in assigned_option_ids:
                    item['n_assigned_options'] += 1
            varieties.append(item)

        # sort varieties by number of assigned options, so that varieties that
        # have been assigned are at the top of the list. The rest remains sorted
        # alphabetically...
        varieties.sort(key=lambda x: -x.get('n_assigned_options', 0))

        # validation
        is_valid = True
        if request.method == 'POST':
            # process sku records
            prefixes = request.POST.getlist('skus')
            assigned_option_ids = []
            skus_to_save = []
            sku_code_processed = []
            barcodes_processed = []
            for index, prefix in enumerate(prefixes):
                # extract relevant informatioin from post for
                # individual combination
                _id = get_var(prefix, '_id')
                d = {
                    'enabled': get_var(prefix, 'enabled') == 'on',
                    'sku': get_var(prefix, 'sku'),
                    'barcode': get_var(prefix, 'barcode'),
                    'price': get_var(prefix, 'price'),
                    'stocklevel': get_int_var(prefix, 'stocklevel', 0)
                }

                # parse assigned variety options from request data
                n_variety_option = 1
                d['variety_options'] = []
                while len(d['variety_options']) <= 16:
                    _name = 'vo_%d' % n_variety_option
                    if has_var(prefix, _name):
                        d['variety_options'].append(get_int_var(prefix, _name))
                        n_variety_option += 1
                    else:
                        break

                # make sure that sku, barcode and price are None
                # instead of empty
                if _id == '': _id = None
                if d.get('sku') == '': d['sku'] = None
                if d.get('barcode') == '': d['barcode'] = None
                if d.get('price') == '': d['price'] = None

                # construct form based on this data and validate
                form = ProductSKUForm(d)
                form.configure(request, barcode_system)

                # get variety options
                variety_options = VarietyOption.objects.filter(
                    pk__in=d.get('variety_options'))

                # create or edit?
                sku = initial.get(_id, None)
                if sku is None:
                    sku = ProductSKU.objects.get_by_variety_options(
                        product, variety_options)

                    # still not found? -> create new item
                    if sku is None:
                        sku = ProductSKU()
                        sku.product = product

                # remember the sku record to be saved once we processed
                # everything. We will not save anything until everything
                # is considered to be valid.
                skus_to_save.append(sku)

                # mark any assigned variety options as selected, so that they
                # indeed remain selected, even if they have actually not been
                # properly assigned yet because if from errors for example
                for _variety in varieties:
                    _options = _variety.get('options')
                    for _option in _options:
                        for _assigned_option in variety_options:
                            if _option.get('id') == _assigned_option.pk:
                                _option['assigned'] = True
                                break

                # inject error information and keep track of error states
                sku.errors = []
                if form.is_valid():
                    # update data from from
                    d = form.cleaned_data
                else:
                    for field, error in form.errors.items():
                        sku.errors.append({'field': field, 'error': error[0]})
                    is_valid = False

                # copy original data or cleaned data
                sku.enabled = d.get('enabled', False)
                sku.sku = d.get('sku')
                sku.barcode = d.get('barcode')
                sku.price = d.get('price')
                sku.stocklevel = d.get('stocklevel', 0)

                # keep track of variety options that should be assigned due to
                # SKU's that are enabled
                if sku.enabled:
                    assigned_option_ids.extend(
                        [option.pk for option in variety_options])

                # set variety options (saved later)
                sku._variety_options = variety_options

                # verify uniqueness of the SKU code
                if not self._verify_sku(product, sku, sku_code_processed,
                                        initial):
                    is_valid = False

                # verify uniqueness of barcode
                if not self._verify_barcode(product, sku, barcodes_processed,
                                            initial):
                    is_valid = False

                # maintain changed data in initial data set, so that all
                # changes make theire way back into the view, even through
                # we might not have saved changes due to errors
                _id = ('idx_%d' % index) if sku.pk is None else sku.pk
                initial[_id] = sku

        # process if everything is valid
        if request.method == 'POST' and is_valid:
            # create missing option assignments
            assigned_option_ids = list(
                set(filter(lambda x: x is not None, assigned_option_ids)))
            for option_id in assigned_option_ids:
                try:
                    assignment = VarietyAssignment.objects.get(
                        product=product, variety_option__pk=option_id)
                except VarietyAssignment.DoesNotExist:
                    VarietyAssignment.objects.create(
                        product=product, variety_option_id=option_id)

            # remove deprecated option assignments
            deprecated_assignments = VarietyAssignment.objects.select_related(
                'variety_option').filter(
                    product=product,
                    variety_option__variety__sku=True).exclude(
                        variety_option__pk__in=assigned_option_ids)
            for deprecated_assignment in deprecated_assignments:
                deprecated_assignment.delete()

            # save changes to sku records. Null sku, so that we would not
            # collide when making updates
            sku_ids_saved = []
            for sku in skus_to_save:
                # save product sku itself
                sku._sku = sku.sku
                sku.sku = None
                sku.save()
                sku_ids_saved.append(sku.id)

                # assign and save variety options
                sku.variety_options = sku._variety_options

            # remove all previous SKU
            deprecated_skus = ProductSKU.objects.filter(
                product=product).exclude(pk__in=sku_ids_saved)
            for deprecated_sku in deprecated_skus:
                deprecated_sku.delete()

            # apply new sku names, which is now safe to do
            for sku in skus_to_save:
                if request.settings.sku_is_barcode:
                    sku.sku = sku.barcode
                else:
                    sku.sku = sku._sku
                sku.save()

        # redirect and message
        if request.method == 'POST' and is_valid:
            messages.add_message(
                request, messages.SUCCESS,
                'Product Varieties and SKUs saved for <em>%s</em>.' %
                product.title)

            if request.POST.get('cubane_save_and_continue') is None:
                return self._redirect(request, 'index')
            else:
                return self._redirect(request, 'sku', product)

        # materialise current initial data
        _initial = {}
        for _id, _sku in initial.items():
            _initial[_id] = model_to_dict(_sku)
            if _sku.pk is None:
                _initial[_id]['variety_options'] = [
                    option.pk for option in _sku._variety_options
                ]
            else:
                _initial[_id]['variety_options'] = [
                    option.pk for option in _sku.variety_options.all()
                ]
            _initial[_id]['errors'] = _sku.errors if hasattr(_sku,
                                                             'errors') else []

        # template context
        return {
            'product': product,
            'varieties': varieties,
            'initial': to_json(_initial),
            'variety_index_json': to_json(variety_index),
            'form_template': form_template
        }

    def _verify_sku(self, product, sku, sku_code_processed, initial):
        """
        Verify that the given SKU does not exist twice in the system.
        """
        is_valid = True

        def add_error(errors, error):
            # already exists?
            for err in errors:
                if err.get('field') == error.get('field') and err.get(
                        'error') == error.get('error'):
                    return
            errors.append(error)

        # empty SKU?
        if not sku.sku:
            return is_valid

        # make sure that the sku number does not conflict with
        # any other product in the system.
        products = get_product_model().objects.filter(sku=sku)
        if products.count() > 0:
            sku.errors.append({
                'field':
                'sku',
                'error':
                'SKU number already in use by product \'%s\'.' %
                products[0].title
            })
            is_valid = False

        # conflicts with any other record we processed so far?
        if sku.sku in sku_code_processed:
            for _, _sku in initial.items():
                if _sku.sku == sku.sku:
                    error = {
                        'field': 'sku',
                        'error': 'SKU number already in use for this product.'
                    }
                    add_error(sku.errors, error)
                    add_error(_sku.errors, error)
            is_valid = False
        sku_code_processed.append(sku.sku)

        # conflict with any other SKU record for any other product?
        product_skus = ProductSKU.objects.exclude(product=product).filter(
            sku=sku.sku)
        if product_skus.count() > 0:
            sku.errors.append({
                'field':
                'sku',
                'error':
                'SKU number already in use by product \'%s\'.' %
                product_skus[0].product.title
            })
            is_valid = False

        return is_valid

    def _verify_barcode(self, product, sku, barcodes_processed, initial):
        """
        Verify that any assigned barcode does not exist twice in the system.
        """
        is_valid = True

        # empty barcode?
        if not sku.barcode:
            return is_valid

        # make sure that the barcode does not conflict with any product
        products = get_product_model().objects.filter(barcode=sku.barcode)
        if products.count() > 0:
            sku.errors.append({
                'field':
                'barcode',
                'error':
                'Barcode already in use by product \'%s\'.' % products[0].title
            })
            is_valid = False

        # conflicts with any other record we processed so far?
        if sku.barcode in barcodes_processed:
            for _, _sku in initial.items():
                if _sku.barcode == sku.barcode:
                    error = {
                        'field': 'barcode',
                        'error': 'Barcode already in use for this product.'
                    }
                    sku.errors.append(error)
                    _sku.errors.append(error)
            is_valid = False
        barcodes_processed.append(sku.barcode)

        # conflict with any other SKU record for any other product?
        product_skus = ProductSKU.objects.exclude(product=product).filter(
            barcode=sku.barcode)
        if product_skus.count() > 0:
            sku.errors.append({
                'field':
                'barcode',
                'error':
                'Barcode already in use by product \'%s\'.' %
                product_skus[0].product.title
            })
            is_valid = False

        return is_valid

    def delivery(self, request):
        product_id = request.GET.get('pk')
        product = get_object_or_404(get_product_model(), pk=product_id)

        # get general delivery options that are available
        options = DeliveryOption.objects.filter(enabled=True)

        # get available delivery options
        delivery_options = list(
            ProductDeliveryOption.objects.select_related(
                'delivery_option').filter(product=product))
        delivery_options_ids = [
            option.delivery_option.id for option in delivery_options
        ]

        # add missing options, so that each is convered
        for option in options:
            if option.id not in delivery_options_ids:
                assignment = ProductDeliveryOption()
                assignment.product = product
                assignment.delivery_option = option
                delivery_options.append(assignment)

        # dataset based on available options
        initial = [{
            'option_id': option.delivery_option.id,
            'deliver_uk': option.delivery_option.deliver_uk,
            'deliver_eu': option.delivery_option.deliver_eu,
            'deliver_world': option.delivery_option.deliver_world,
            'title': option.delivery_option.title,
            'uk': option.uk,
            'eu': option.eu,
            'world': option.world
        } for option in delivery_options]

        if request.method == 'POST':
            formset = DeliveryOptionFormset(request.POST, initial=initial)
        else:
            formset = DeliveryOptionFormset(initial=initial)

        if request.method == 'POST':
            if formset.is_valid():
                # delete all existing assignments
                assignments = ProductDeliveryOption.objects.filter(
                    product=product)
                for assignment in assignments:
                    request.changelog.delete(assignment)
                    assignment.delete()

                # create new assignments
                for form in formset.forms:
                    d = form.cleaned_data
                    for option in options:
                        if option.id == d.get('option_id'):
                            assignment = ProductDeliveryOption()
                            assignment.product = product
                            assignment.delivery_option = option
                            assignment.uk = d.get('uk')
                            assignment.eu = d.get('eu')
                            assignment.world = d.get('world')
                            assignment.save()
                            request.changelog.create(assignment)
                            break

                # commit, message and redirect
                request.changelog.commit(
                    'Delivery options for product <em>%s</em> updated.' %
                    product.title,
                    product,
                    flash=True)
                return self.redirect_to_index_or(request, 'delivery', product)
            else:
                print formset.errors

        return {
            'product': product,
            'delivery_options': delivery_options,
            'form': formset
        }

    def google_products(self, request):
        def prettify_xml(elem):
            """
            Return a pretty-printed XML string for the Element.
            """
            rough_string = tostring(elem)
            reparsed = minidom.parseString(rough_string)
            return reparsed.toprettyxml(indent='\t').encode('utf-8', 'replace')

        products = get_product_model().objects.filter(feed_google=True)
        root = Element('rss')
        root.attrib['xmlns:g'] = 'http://base.google.com/ns/1.0'
        root.attrib['version'] = '2.0'
        channel = SubElement(root, 'channel')
        title = SubElement(channel, 'title')
        title.text = request.settings.name
        link = SubElement(channel, 'link')
        link.text = settings.DOMAIN_NAME
        description = SubElement(channel, 'description')

        for p in products:
            # availability
            if p.is_available and not p.pre_order:
                txt_availability = 'in stock'
            elif p.pre_order:
                txt_availability = 'preorder'
            else:
                txt_availability = 'out of stock'

            # determine delivery charge by placing the product onto the basket
            basket = Basket()
            basket.add_item(p, None, 1)
            delivery_charge = basket.delivery

            # determine feed item attributes
            txt_id = unicode(p.id)
            txt_title = clean_unicode(p.title).strip()
            txt_link = p.get_absolute_url()
            txt_description = text_from_html(p.description, 5000)
            txt_condition = 'new'
            txt_price = '%.2f GBP' % p.price
            txt_google_category = p.category.google_product_category if p.category and p.category.google_product_category else None
            txt_category = p.category.get_taxonomy_path(
            ) if p.category else None
            txt_country = 'GB'
            txt_delivery_price = '%s %s' % (delivery_charge, 'GBP')
            txt_barcode = p.barcode.strip() if p.barcode else None
            txt_part_number = p.part_number.strip() if p.part_number else None
            txt_brand = p.get_brand_title()

            # create item
            item = SubElement(channel, 'item')

            # id
            _id = SubElement(item, 'g:id')
            _id.text = txt_id

            # title
            title = SubElement(item, 'title')
            title.text = txt_title

            # link/url
            link = SubElement(item, 'link')
            link.text = txt_link

            # main text
            description = SubElement(item, 'description')
            description.text = txt_description

            # condition
            condition = SubElement(item, 'g:condition')
            condition.text = txt_condition

            # price
            price = SubElement(item, 'g:price')
            price.text = txt_price

            # availability
            availability = SubElement(item, 'g:availability')
            availability.text = txt_availability

            # google shopping category
            if txt_google_category:
                gcategory = SubElement(item, 'g:google_product_category')
                gcategory.text = txt_google_category

            # product type
            if txt_category:
                category = SubElement(item, 'g:product_type')
                category.text = txt_category

            # shipping
            shipping = SubElement(item, 'g:shipping')

            # country
            country = SubElement(shipping, 'g:country')
            country.text = txt_country

            # delivery price
            delivery_price = SubElement(shipping, 'g:price')
            delivery_price.text = txt_delivery_price

            # barcode, must be a valid UPC-A (GTIN-12), EAN/JAN (GTIN-13)
            # or GTIN-14, so we need to have at least 12 characters.
            if txt_barcode:
                gtin = SubElement(item, 'g:gtin')
                gtin.text = txt_barcode

            # part number
            if txt_part_number:
                _mpn = SubElement(item, 'g:mpn')
                _mpn.text = txt_part_number

            # brand
            if txt_brand:
                brand = SubElement(item, 'g:brand')
                brand.text = txt_brand

            # image
            if p.image:
                image = SubElement(item, 'g:image_link')
                image.text = p.image.large_url

            # additional images
            if len(p.gallery) > 0:
                for m in p.gallery[:10]:
                    additional_image_link = SubElement(
                        item, 'g:additional_image_link')
                    additional_image_link.text = m.large_url

        # get temp. filename
        f = NamedTemporaryFile(delete=False)
        tmp_filename = f.name
        f.close()

        # create tmp file (utf-8)
        f = open(tmp_filename, 'w+b')
        f.write(prettify_xml(root))
        f.seek(0)

        # send response
        filename = 'google_products_%s.xml' % datetime.date.today().strftime(
            '%d_%m_%Y')
        response = HttpResponse(FileWrapper(f), content_type='text/plain')
        response['Content-Disposition'] = 'attachment; filename=%s' % filename
        return response
Exemple #5
0
class CustomerView(ModelView):
    template_path = 'cubane/ishop/merchant/customers/'
    namespace = 'cubane.ishop.customers'
    model = get_customer_model()

    patterns = [
        view_url(r'change-password/$',
                 view='change_password',
                 name='change_password'),
    ]

    listing_actions = [('[Change Password]', 'change_password', 'single')]

    shortcut_actions = [
        'change_password',
    ]

    def __init__(self, *args, **kwargs):
        if not settings.SHOP_CHANGE_CUSTOMER_PASSWORD_ENABLED:
            self.shortcut_actions = []
            self.listing_actions = []
        super(CustomerView, self).__init__(*args, **kwargs)

    def _get_objects(self, request):
        return self.model.objects.all()

    @view(require_POST)
    @view(permission_required('delete'))
    def delete(self, request, pk=None):
        """
        Delete existing model instance with given primary key pk or (if no
        primary key is given in the url) attempt to delete multiple entities
        that are given by ids post argument.
        """
        # determine list of pks
        pks = []
        if pk:
            pks = [pk]
        else:
            pks = request.POST.getlist('pks[]', [])
            if len(pks) == 0:
                pk = request.POST.get('pk')
                if pk:
                    pks = [pk]

        # delete instance(s)...
        if len(pks) == 1:
            instance = self.get_object_or_404(request, pks[0])
            label = instance.__unicode__()
            if not label: label = '<no label>'
            instance.delete()
            message = self._get_success_message(label, 'deleted')
        else:
            instances = self._get_objects(request).filter(pk__in=pks)
            for instance in instances:
                instance.delete()
            message = '%d %s deleted successfully.' % (
                len(instances), self.model._meta.verbose_name_plural)

        # response
        if self._is_json(request):
            return to_json_response({'success': True, 'message': message})
        else:
            messages.add_message(request, messages.SUCCESS, message)
            return self._redirect(request, 'index')

    def change_password(self, request):
        # feature enabled?
        if not settings.SHOP_CHANGE_CUSTOMER_PASSWORD_ENABLED:
            raise Http404('Feature is not enabled.')

        customer_id = request.GET.get('pk')
        try:
            customer = self.model.objects.get(pk=customer_id)
            user = customer.user
        except self.model.DoesNotExist:
            raise Http404('Unknown customer account id %s.' % customer_id)

        if request.method == 'POST':
            form = CustomerChangePasswordForm(request.POST)
        else:
            form = CustomerChangePasswordForm()

        if request.method == 'POST' and form.is_valid():
            user.set_password(form.cleaned_data['password'])
            user.save()
            messages.add_message(
                request, messages.SUCCESS,
                'Password changed for <em>%s</em>.' % customer.email)
            return self._redirect(request, 'index')

        return {'customer': customer, 'form': form}

    @view(csrf_exempt)
    def export(self, request):
        return _export(request)
Exemple #6
0
class SitemapView(TemplateView):
    """
    Edit cms content by navigating the main sitemap structure of the website.
    """
    template_path = 'cubane/cms/sitemap/'
    namespace = 'cubane.cms.sitemap'


    patterns = [
        view_url(r'^$',      'index', name='index'),
        view_url(r'^node/$', 'node',  name='node'),
    ]


    def __init__(self, *args, **kwargs):
        super(SitemapView, self).__init__(*args, **kwargs)


    def index(self, request):
        return {}


    def node(self, request):
        # get cms
        from cubane.cms.views import get_cms
        cms = get_cms()

        # root level (pages and directory categories)
        if 'pk' not in request.GET and 'type' not in request.GET:
            return to_json_response({
                'success': True,
                'items': [cms.get_sitemap_item(request, page) for page in cms.get_sitemap_root_pages()]
            })

        # get pk argument
        if 'pk' not in request.GET:
            raise Http404('Missing argument: pk.')
        try:
            pk = int(request.GET.get('pk'))
        except ValueError:
            raise Http404('Invalid pk argument: Not a number.')

        # get type argument
        if 'type' not in request.GET:
            raise Http404('Missing argument: type.')
        type_name = request.GET.get('type')

        # resolve type by given name
        model = None
        for m in get_models():
            if m.__name__ == type_name:
                model = m
                break
        if not model:
            raise Http404('Unknown model type name: \'%s\'.' % type_name)

        # get node by given pk
        node = model.objects.get(pk=pk)

        # generate child nodes
        items = []
        children = cms.get_sitemap_children(node)
        if children:
            for child in children:
                item = cms.get_sitemap_item(request, child)
                if item:
                    items.append(item)

        # return response (json)
        return to_json_response({
            'success': True,
            'items': items
        })
Exemple #7
0
class MediaView(ModelView):
    """
    Base class for editing media assets.
    """
    template_path = 'cubane/media/'
    model = Media
    folder_model = MediaFolder
    form = MediaForm

    PATTERNS = [view_url(r'download/', 'download', name='download')]

    LISTING_ACTIONS = [('Download', 'download', 'multiple')]

    SHORTCUT_ACTIONS = ['download']

    def __init__(self, *args, **kwargs):
        self.patterns = copy.copy(self.PATTERNS)
        self.listing_actions = copy.copy(self.LISTING_ACTIONS)
        self.shortcut_actions = copy.copy(self.SHORTCUT_ACTIONS)

        # auto fit
        self.exclude_columns = []
        if not settings.IMAGE_FITTING_ENABLED or not settings.IMAGE_FITTING_SHAPES:
            self.exclude_columns.append('auto_fit')

        # provide media sharing if CMS is used
        if 'cubane.cms' in settings.INSTALLED_APPS:
            self.patterns.append(view_url(r'share/', 'share', name='share'))
            self.listing_actions.append(('[/Share]', 'share', 'single'))
            self.shortcut_actions.append('share')
        else:
            self.exclude_columns.append('share_enabled')

        super(MediaView, self).__init__(*args, **kwargs)

    def before_save(self, request, cleaned_data, instance, edit):
        """
        Detect changes of media instance regarding image-relevant properties,
        such as auto-fit or compression quality.
        """
        auto_fit_changed = settings.IMAGE_FITTING_ENABLED and settings.IMAGE_FITTING_SHAPES and instance.auto_fit != cleaned_data.get(
            'auto_fit')
        quality_changed = instance.jpeg_quality != cleaned_data.get(
            'jpeg_quality')
        focal_point_changed = instance.focal_x != cleaned_data.get(
            'focal_x') or instance.focal_y != cleaned_data.get('focal_y')

        if auto_fit_changed or quality_changed or focal_point_changed:
            instance._image_config_changed = True
        else:
            instance._image_config_changed = False

    def after_save(self, request, cleaned_data, instance, edit):
        changed = False
        if 'media' in request.FILES:
            instance.upload_from_stream(request.FILES['media'],
                                        request=request)
            changed = True
        elif instance._image_config_changed:
            # edit -> if we changed image-relevant configuration then we need
            # to re-generate image shapes...
            instance.upload(request=request)
            changed = True

        # image edited -> increase version number
        if edit and changed:
            instance.increase_version()

    def before_bulk_save(self, request, cleaned_data, instance, edit):
        """
        Called before the given model instance is saved as part of bulk editing.
        """
        self.before_save(request, cleaned_data, instance, edit)

    def after_bulk_save(self, request, cleaned_data, instance, edit):
        """
        Called after the given model instance is saved as part of bulk editing.
        """
        self.after_save(request, cleaned_data, instance, edit)

    def _get_folders(self, request, parent):
        folders = self.folder_model.objects.all()

        if parent:
            folders = folders.filter(parent=parent)

        return folders

    def _folder_filter(self, request, objects, folder_pks):
        if folder_pks:
            q = Q()
            for pk in folder_pks:
                q |= Q(parent_id=pk) | \
                     Q(parent__parent_id=pk) | \
                     Q(parent__parent__parent_id=pk) | \
                     Q(parent__parent__parent__parent_id=pk) | \
                     Q(parent__parent__parent__parent__parent_id=pk) | \
                     Q(parent__parent__parent__parent__parent__parent_id=pk)
            objects = objects.filter(q)
        return objects

    def after(self, request, handler, response):
        if isinstance(response, dict):
            response['multi_url'] = self.namespace + '.create_multi'
        return super(MediaView, self).after(request, handler, response)

    @property
    def listing_with_image(self):
        return True

    def _create_edit(self, request, pk=None, edit=False, duplicate=False):
        if not edit and len(request.FILES.getlist('media')) > 1:
            # cancel?
            if request.POST.get('cubane_form_cancel', '0') == '1':
                return self._redirect(request, 'index')

            kwargs = {}

            # create form
            if request.method == 'POST':
                form = MultiMediaForm(request.POST, request.FILES, **kwargs)
            else:
                form = MultiMediaForm(**kwargs)

            form.configure(request)

            # validate form
            if request.method == 'POST' and form.is_valid():
                # update properties in model instance
                d = form.cleaned_data

                if 'media' in request.FILES:
                    file_list = request.FILES.getlist('media')
                    n = 0
                    Progress.start(request, len(file_list))
                    first_instance = None
                    for f in file_list:
                        instance = self.model()
                        instance.parent = d.get('parent')

                        if first_instance is None:
                            first_instance = instance

                        # auto fit
                        if settings.IMAGE_FITTING_ENABLED and settings.IMAGE_FITTING_SHAPES:
                            instance.auto_fit = d.get('auto_fit')

                        # jpeg quality
                        instance.jpeg_quality = d.get('jpeg_quality')

                        # save
                        instance.save()
                        instance.upload_save_from_stream(f, request=request)
                        n += 1

                        request.changelog.create(instance)

                        # notify task runner to do the job
                        if instance.is_blank:
                            TaskRunner.notify()

                        # report progress made
                        Progress.set_progress(request, n)

                    # commit changes
                    message = '<em>%d</em> %s created.' % (
                        n, self.model._meta.verbose_name_plural)
                    change = request.changelog.commit(message,
                                                      model=self.model)

                    Progress.stop(request)

                    # ajax operation, simply return success and message
                    # information
                    if request.is_ajax():
                        return to_json_response({
                            'success':
                            True,
                            'message':
                            message,
                            'change':
                            change,
                            'next':
                            self._get_url(request, 'index', namespace=True),
                            'instance_id':
                            first_instance.pk,
                            'instance_title':
                            unicode(first_instance),
                        })

                # dialog or screen?
                if request.GET.get('create', 'false') == 'true':
                    return {
                        'dialog_created_id': instance.pk,
                        'dialog_created_title': unicode(instance)
                    }
                else:
                    return self._redirect(request, 'index')
            elif request.is_ajax():
                return to_json_response({
                    'success': False,
                    'errors': form.errors
                })

            context = {
                'form': form,
                'permissions': {
                    'create': self.user_has_permission(request.user, 'add'),
                    'view': self.user_has_permission(request.user, 'view'),
                    'edit': self.user_has_permission(request.user, 'edit')
                },
                'verbose_name': 'Multiple Media'
            }
        else:
            if request.method == 'POST':
                Progress.start(request, 1)

            context = super(MediaView,
                            self)._create_edit(request, pk, edit, duplicate)

        if isinstance(context, dict):
            context['is_images'] = isinstance(self, ImageView)

        return context

    def download(self, request):
        """
        Download individual or multiple media assets. Multiple media assets are
        zipped.
        """
        # get pks
        pks = get_pks(request.GET)

        # get list of media
        media = Media.objects.filter(pk__in=pks).order_by('caption')
        n_media = media.count()

        # no media?
        if n_media == 0:
            raise Http404(
                'Unable to export media assets for empty list of media objects.'
            )

        # single media item?
        if n_media == 1:
            item = media.first()
            response = FileResponse(open(item.original_path, 'rb'))
            response[
                'Content-Disposition'] = 'attachment; filename="%s"' % item.filename
            return response

        # multiple assets -> create zip file
        f = NamedTemporaryFile()
        zf = zipfile.ZipFile(f, 'w')

        # attach original files to zip, handle duplicated filenames
        filenames = {}
        for item in media:
            # determine unique filename
            filename = item.filename
            if filename not in filenames:
                filenames[filename] = 1
            else:
                fn = filename
                base, ext = os.path.splitext(filename)
                while fn in filenames:
                    filenames[fn] += 1
                    fn = '%s-%d%s' % (base, filenames[fn], ext)
                filename = fn
                filenames[filename] = 1

            # attach file to zip...
            zf.write(item.original_path, filename)
        zf.close()
        f.seek(0)

        # determine site name from settings (CMS)
        if 'cubane.cms' in settings.INSTALLED_APPS:
            from cubane.cms.views import get_cms_settings
            site_name = get_cms_settings().name
            if site_name is None: site_name = ''
            site_name = slugify(site_name)
            if site_name != '': site_name += '_'
        else:
            site_name = ''

        # generate download filename for zip file
        today = datetime.date.today()
        filename = '{0}media_{1:02d}_{2:02d}_{3:04d}.zip'.format(
            site_name, today.day, today.month, today.year)

        # serve file
        response = FileResponse(f)
        response[
            'Content-Disposition'] = 'attachment; filename="%s"' % filename
        return response

    def share(self, request):
        # get media asset
        pk = request.GET.get('pk')
        media = get_object_or_404(Media, pk=pk)

        # create form
        if request.method == 'POST':
            form = MediaShareForm(request.POST)
        else:
            share_filename = media.share_filename
            if not share_filename:
                share_filename = media.filename

            form = MediaShareForm(
                initial={
                    'share_enabled': media.share_enabled,
                    'share_filename': share_filename
                })

        # configure form
        form.configure(request, instance=media, edit=True)

        # validate form
        if request.method == 'POST' and form.is_valid():
            d = form.cleaned_data

            if media.share_enabled != d.get(
                    'share_enabled') or media.share_filename != d.get(
                        'share_filename'):
                previous_instance = request.changelog.get_changes(media)

                media.share_enabled = d.get('share_enabled')
                media.share_filename = d.get('share_filename')
                media.save()

                request.changelog.edit(media, previous_instance)
                request.changelog.commit(
                    'File sharing %s: <em>%s</em>.' %
                    ('enabled' if media.share_enabled else 'disabled',
                     media.caption),
                    media,
                    flash=True)

            return self._redirect(request, 'index')

        return {
            'form': form,
            'media': media,
            'base_url': make_absolute_url(settings.MEDIA_DOWNLOAD_URL)
        }
Exemple #8
0
class VarietyView(ModelView):
    """
    Editing varieties.
    """
    template_path = 'cubane/ishop/merchant/varieties/'
    namespace = 'cubane.ishop.varieties'
    folder_model = Variety
    model = Variety
    form = VarietyForm


    patterns = [
        view_url(r'options/', 'options', name='options'),
        view_url(r'products/', 'products', name='products'),
        view_url(r'update\-seq/(?P<variety_id>\d+)/', 'update_seq', name='update_seq'),
    ]

    listing_actions = [
        ('[Options]', 'options', 'single'),
        ('[Products]', 'products', 'single'),
    ]

    shortcut_actions = [
        'options',
        'products'
    ]


    def _get_objects(self, request):
        return self.model.objects.prefetch_related(
            Prefetch('options', queryset=VarietyOption.objects.order_by('seq'))
        ).annotate(
            num_products=Count('options__assignments__product', distinct=True)
        )


    def _get_objects_for_seq(self, request):
        return self.model.objects.all()


    def _get_folders(self, request, parent):
        folders = self.folder_model.objects.all()

        if parent:
            folders = folders.filter(parent=parent)

        return folders


    def options(self, request):
        # load variety
        variety_id = request.GET.get('pk')
        variety = get_object_or_404(Variety, pk=variety_id)

        # load import variety
        import_variety_id = request.GET.get('import_variety')
        import_variety = None
        if import_variety_id:
            try:
                import_variety = Variety.objects.get(pk=import_variety_id)
            except variety.DoesNotExist:
                pass

        # get current list of options, or if we are importing from the
        # given source
        if import_variety:
            options = list(import_variety.options.order_by('seq'))
            for option in options:
                option.id = None
                option.variety = None
        else:
            options = list(variety.options.order_by('seq', 'id'))

        initial = [{
            'title': option.title,
            'enabled': option.enabled,
            'image': option.image,
            'color': option.color,
            'offset_type': option.default_offset_type,
            'offset_value': option.default_offset_value,
            'text_label': option.text_label,
            'seq': seq,
            '_id': option.id
        } for seq, option in enumerate(options, start=1)]

        # determine form (attribute or variety)
        if variety.is_attribute:
            form_class = VarietyAttributeFormset
        else:
            form_class = VarietyFormset

        # create main form
        if request.method == 'POST':
            formset = form_class(request.POST, initial=initial)
        else:
            formset = form_class(initial=initial)

        # form validation
        if request.method == 'POST':
            if request.POST.get('cubane_form_cancel', '0') == '1':
                return self._redirect(request, 'index')

            if formset.is_valid():
                seq = 0
                for form in formset.forms:
                    d = form.cleaned_data

                    try:
                        option = filter(lambda o: o.id == d.get('_id'), options)[0]
                    except IndexError:
                        option = VarietyOption()

                    if d.get('DELETE') == False and d.get('title'):
                        option.title = d.get('title')
                        option.enabled = d.get('enabled')
                        option.image = d.get('image')
                        option.color = d.get('color')
                        option.seq   = d.get('seq')
                        option.text_label = d.get('text_label')
                        option.variety = variety

                        if option.seq is None:
                            option.seq = seq + 1
                            seq += 1

                        if option.seq > seq:
                            seq = option.seq

                        if not variety.is_attribute:
                            option.default_offset_type = d.get('offset_type')
                            option.default_offset_value = d.get('offset_value')

                        # get previous state
                        if option.pk is not None:
                            previous_instance = request.changelog.get_changes(option)
                        else:
                            previous_instance = None

                        # save option
                        option.save()

                        # generate changelog
                        if previous_instance:
                            request.changelog.edit(option, previous_instance)
                        else:
                            request.changelog.create(option)

                    elif d.get('DELETE') == True and option.id != None:
                        # delete option
                        request.changelog.delete(option)
                        option.delete()

                request.changelog.commit(
                    'Variety Options for <em>%s</em> updated.' % variety.title,
                    variety,
                    flash=True
                )
                return self.redirect_to_index_or(request, 'options', variety)

        # create import form
        if request.method == 'GET':
            import_form = VarietyOptionImportForm()
            import_form.configure(request, variety)
        else:
            import_form = None

        return {
            'variety': variety,
            'form': formset,
            'import_form': import_form,
            'kit_builder': settings.SHOP_ENABLE_KIT_BUILDER
        }


    def products(self, request):
        variety_id = request.GET.get('pk')
        variety = get_object_or_404(Variety, pk=variety_id)

        assignments = VarietyAssignment.objects.filter(variety_option__variety=variety).values('product_id').distinct()
        product_ids = [a.get('product_id') for a in assignments]

        # get products
        if settings.SHOP_MULTIPLE_CATEGORIES:
            products = get_product_model().objects.prefetch_related(
                Prefetch('categories', queryset=ProductCategory.objects.select_related('category').order_by('seq'))
            ).distinct()
        else:
            products = get_product_model().objects.select_related('category')
        products = products.filter(pk__in=product_ids).order_by('title')

        if request.method == 'POST':
            return self._redirect(request, 'index')

        return {
            'variety': variety,
            'products': products
        }


    def combine(self, request):
        variety_ids = request.GET.getlist('pks[]')

        # get target
        if len(variety_ids) > 0:
            target = Variety.objects.get(pk=variety_ids[0])
        else:
            target = None

        # get sources
        sources = []
        if len(variety_ids) > 1:
            varieties = Variety.objects.in_bulk(variety_ids[1:])
            for vid in variety_ids[1:]:
                sources.append(varieties.get(int(vid)))

        # get combines options
        options = list(target.options.all())
        for s in sources:
            options.extend(list(s.options.all()))

        if request.method == 'POST':
            if request.POST.get('cubane_form_cancel', '0') != '1':
                # build index for target
                labels = {}
                for option in target.options.all():
                    labels[option.title] = option

                # process all sources
                obsolete_options = []
                for variety in sources:
                    for option in variety.options.all().order_by('seq'):
                        if option.title in labels:
                            # already exists. Now we have to change all assignments over to point to the new one in target
                            for assignment in VarietyAssignment.objects.filter(variety_option=option):
                                assignment.variety_option = labels.get(option.title)
                                assignment.save()
                            obsolete_options.append(option)
                        else:
                            # does not exist yet, simply change the option over to point to the target variety
                            option.variety = target
                            option.save()
                            labels[option.title] = option

                # remove obsolete options
                [option.delete() for option in obsolete_options]

                # remove source varieties, since they all have been combined into target.
                [variety.delete() for variety in sources]

                messages.add_message(request, messages.SUCCESS, 'Varieties Combined successfully into <em>%s</em>.' % target.title)

            return self._redirect(request, 'index')

        return {
            'target': target,
            'sources': sources,
            'options': options
        }


    @view(require_POST)
    def update_seq(self, request, variety_id=None):
        variety = get_object_or_404(Variety, pk=variety_id)

        option_ids = [parse_int(x, 0) for x in request.POST.getlist('option[]')]
        if any(map(lambda x: x == 0, option_ids)):
            raise Http404('Unable to parse option id list.')

        options = list(variety.options.order_by('seq', 'id'))
        for i, oid in enumerate(option_ids, start = 1):
            o = [o for o in options if o.id == oid][0]
            o.seq = i
            o.save()

        return to_json_response({'success': True})
Exemple #9
0
class InventoryView(ModelView):
    model = ProductSKU
    namespace = 'cubane.ishop.inventory'
    template_path = 'cubane/ishop/merchant/inventory/'

    patterns = [
        view_url(r'inventory-import/',
                 'inventory_import',
                 name='inventory_import'),
        view_url(r'inventory-export/',
                 'inventory_export',
                 name='inventory_export'),
    ]

    listing_actions = [('Import / Export', 'inventory_import', 'any')]

    def _get_objects(self, request):
        return ProductSKU.objects.prefetch_related(
            Prefetch('variety_options',
                     queryset=VarietyOption.objects.order_by('seq')))

    def before_save(self, request, cleaned_data, instance, edit):
        """
        Save barcode as SKU number
        """
        if request.settings.sku_is_barcode:
            instance.sku = cleaned_data.get('barcode')

    def after_save(self, request, cleaned_data, instance, edit):
        """
        Maintain variety assignments
        """
        for variety_option in instance.variety_options.all():
            try:
                assignment = VarietyAssignment.objects.get(
                    product=instance.product, variety_option=variety_option)
            except VarietyAssignment.DoesNotExist:
                VarietyAssignment.objects.create(product=instance.product,
                                                 variety_option=variety_option)

    def before_delete(self, request, instance):
        """
        Delete variety assignments
        """
        for variety_option in instance.variety_options.select_related(
                'variety').all():
            if variety_option.variety.sku:
                try:
                    assignment = VarietyAssignment.objects.get(
                        product=instance.product,
                        variety_option=variety_option)
                    assignment.delete()
                except VarietyAssignment.DoesNotExist:
                    pass

    def inventory_import(self, request):
        """
        Shop Inventory Import (CSV).
        """
        # default encoding
        initial = {'encoding': get_cms_settings().default_encoding}

        if request.method == 'POST':
            form = ShopInventoryImportForm(request.POST, request.FILES)
        else:
            form = ShopInventoryImportForm(initial=initial)
            form.configure(request)

            export_form = ShopInventoryExportForm(initial=initial)
            export_form.configure(request)

        if request.method == 'POST' and form.is_valid():
            d = form.cleaned_data

            # create importer and import date
            importer = ShopInventoryImporter()
            importer.import_from_stream(request,
                                        request.FILES['csvfile'],
                                        encoding=d.get('encoding'))

            # present information what happend during import
            if importer.has_errors:
                transaction.rollback()

                errors = importer.get_formatted_errors()
                messages.add_message(
                    request, messages.ERROR,
                    ('Import failed due to %s. No data ' +
                     'has been imported. Please correct all issues ' +
                     'and try again.') %
                    pluralize(len(errors), 'error', tag='em'))

                for message in errors:
                    messages.add_message(request, messages.ERROR, message)

                # redirect to itself if we have errors
                return HttpResponseRedirect(
                    get_absolute_url(
                        'cubane.ishop.inventory.inventory_import'))
            else:
                # success message, render image processing page
                messages.add_message(
                    request, messages.SUCCESS,
                    pluralize(importer.num_records_processed,
                              'record',
                              'processed successfully',
                              tag='em'))

                # redirect to itself if we have errors
                return HttpResponseRedirect(
                    get_absolute_url(
                        'cubane.ishop.inventory.inventory_import'))

        return {
            'form':
            form,
            'export_form':
            export_form,
            'export_form_action':
            reverse('cubane.ishop.inventory.inventory_export')
        }

    def inventory_export(self, request):
        """
        Shop Inventory Export (CSV).
        """
        # create exporter and export inventory data
        exporter = ShopInventoryExporter()
        f = exporter.export_to_temp_file(encoding=request.GET.get('encoding'))

        # generate filename based on today's date
        cms_settings = get_cms_settings()
        today = datetime.date.today()
        filename = '{0}__{1:02d}_{2:02d}_{3:04d}.csv'.format(
            slugify(cms_settings.name).replace('-', '_'), today.day,
            today.month, today.year)

        # serve file
        response = FileResponse(f)
        response['Content-Type'] = 'text/csv'
        response[
            'Content-Disposition'] = 'attachment; filename="%s"' % filename
        return response

    def old_data_importer(self, request):
        """
        Import Form.
        """
        if request.method == 'POST':
            form = ShopDataImportForm(request.POST, request.FILES)
        else:
            form = ShopDataImportForm()
            form.configure(request)

            export_form = ShopInventoryExportForm()
            export_form.configure(request)

        if request.method == 'POST' and form.is_valid():
            d = form.cleaned_data

            # load data importer
            import_classname = settings.CUBANE_SHOP_IMPORT.get(
                'importer',
                'cubane.ishop.apps.merchant.dataimport.importer.ShopDataImporter'
            )
            import_class = get_class_from_string(import_classname)

            # create importer and start importing...
            importer = import_class(import_images=d.get('import_images'))
            importer.import_from_stream(request,
                                        request.FILES['csvfile'],
                                        encoding=d.get('encoding'))

            # present information what happend during import
            if importer.has_errors:
                transaction.rollback()

                errors = importer.get_formatted_errors()
                messages.add_message(
                    request, messages.ERROR,
                    ('Import failed due to %s. No data ' +
                     'has been imported. Please correct all issues ' +
                     'and try again.') %
                    pluralize(len(errors), 'error', tag='em'))

                for message in errors:
                    messages.add_message(request, messages.ERROR, message)

                # redirect to itself if we have errors
                return HttpResponseRedirect(
                    get_absolute_url('cubane.ishop.dataimport.index'))
            else:
                # success message, render image processing page
                messages.add_message(
                    request, messages.SUCCESS,
                    pluralize(importer.num_records_processed,
                              'record',
                              'processed successfully',
                              tag='em'))

                # redirect to itself if we have errors
                return HttpResponseRedirect(
                    get_absolute_url('cubane.ishop.dataimport.index'))

        return {'form': form, 'export_form': export_form}