Exemple #1
0
    def test_should_render_correct_markup_for_entities_without_image(self):
        p1 = Page.objects.create(title='A', slug='a')
        p2 = Page.objects.create(title='B', slug='b')

        field = ModelCollectionField(queryset=Page.objects.all())
        html = field.widget.render('fieldname', [p1.pk, p2.pk, 999])

        self.assertIn('<div class="cubane-listing-item collection-item cubane-listing-grid-item" title="A" data-id="%s">' % p1.pk, html)
        self.assertIn('<div class="cubane-listing-item collection-item cubane-listing-grid-item" title="B" data-id="%s">' % p2.pk, html)

        p1.delete()
        p2.delete()
Exemple #2
0
 def test_should_render_correct_markup_for_entities_with_image(self):
     m = Media.objects.create(caption='A Picture', filename='a.jpg', is_image=True, width=512, height=512)
     p1 = Page.objects.create(title='A', slug='a', image=m)
     p2 = Page.objects.create(title='B', slug='b', image=m)
     try:
         field = ModelCollectionField(queryset=Page.objects.all())
         html = field.widget.render('fieldname', [p1.pk, p2.pk, 999])
         self.assertIn('value="%s"' % p1.pk, html)
         self.assertIn('value="%s"' % p2.pk, html)
         self.assertNotIn('value="%s"' % 999, html)
         self.assertIn('data-background-image data-shape="original" data-path="/0/%d/a.jpg"' % m.pk, html)
     finally:
         p1.delete()
         p2.delete()
Exemple #3
0
class ProductFormBase(BaseModelForm):
    class Meta:
        model = get_product_model()
        exclude = ['seq', 'varieties', 'delivery_options', '_related_products']
        widgets = {
            'title':
            forms.TextInput(attrs={
                'class': 'slugify',
                'autocomplete': 'off'
            }),
            'slug':
            forms.TextInput(attrs={
                'class': 'slug',
                'autocomplete': 'off'
            }),
            'price':
            BootstrapTextInput(prepend=settings.CURRENCY,
                               attrs={'class': 'input-medium'}),
            'deposit':
            BootstrapTextInput(prepend=settings.CURRENCY,
                               attrs={'class': 'input-medium'}),
            'rrp':
            BootstrapTextInput(prepend=settings.CURRENCY,
                               attrs={'class': 'input-medium'}),
            'previous_price':
            BootstrapTextInput(prepend=settings.CURRENCY,
                               attrs={'class': 'input-medium'}),
            'finance_options':
            forms.CheckboxSelectMultiple()
        }
        tabs = [{
            'title':
            'Title',
            'fields': [
                'title', 'slug', 'category', 'categories', 'legacy_url',
                'excerpt', '_meta_title', '_meta_description',
                '_meta_keywords', '_meta_preview'
            ]
        }, {
            'title': 'Content',
            'fields': ['_excerpt', 'description']
        }, {
            'title':
            'Price and Availability',
            'fields': [
                'rrp',
                'previous_price',
                'price',
                'draft',
                'non_returnable',
                'collection_only',
                'pre_order',
                'deposit',
                'loan_exempt',
                'finance_options',
                'exempt_from_free_delivery',
                'exempt_from_discount',
                'feed_google',
                'feed_amazon',
                'stock',
                'stocklevel',
                'sku_enabled',
                'sku',
                'barcode_system',
                'barcode',
                'part_number',
            ]
        }, {
            'title': 'Gallery',
            'fields': ['image', '_gallery_images']
        }, {
            'title': 'Related Products',
            'fields': ['_related_products_collection']
        }, {
            'title': 'SKU / Inventory',
            'fields': ['_inventory']
        }]
        sections = {
            'title': 'Product Data',
            '_excerpt': 'Excerpt',
            '_meta_title': 'Meta Data',
            'barcode_system': 'Identification',
            'stock': 'Stock',
            'sku_enabled': 'SKU / Inventory',
            '_meta_preview': 'Search Result Preview',
            'rrp': 'Price',
            'draft': 'Options',
            'pre_order': 'Pre-order',
            'loan_exempt': 'Finance',
            'exempt_from_free_delivery': 'Exemption',
            'feed_google': 'Channel Feeds',
            'image': 'Product Images'
        }

    category = BrowseCategoryField(
        required=True, help_text='The category this product is listed under.')

    _meta_preview = fields.Field(
        label=None,
        required=False,
        help_text='This preview is for demonstration purposes only ' +\
            'and the actual search result may differ from the preview.',
    )

    image = BrowseImagesField(
        required=False,
        help_text=
        'Choose the main image for this product that is used on the product listing page.'
    )

    _gallery_images = GalleryField(
        label='Image Gallery',
        required=False,
        queryset=Media.objects.filter(is_image=True),
        help_text=
        'Add an arbitrarily number of images that are presented on the product details page.'
    )

    _related_products_collection = ModelCollectionField(
        label='Related Products',
        required=False,
        queryset=get_product_model().objects.all(),
        url='/admin/products/',
        title='Products',
        model_title='Products',
        help_text=
        'Add an arbitrarily number of related products to this product.')

    categories = ModelCollectionField(
        label='Categories',
        add_label='Add Category',
        required=True,
        queryset=get_category_model().objects.all(),
        url='/admin/categories/',
        title='Categories',
        model_title='Categories',
        viewmode=ModelCollectionField.VIEWMODE_LIST,
        allow_duplicates=False,
        sortable=False,
        help_text=
        'Add an arbitrarily number of categories this product is listed under.'
    )

    _inventory = RelatedListingField(view=InventoryView())

    def configure(self, request, instance, edit):
        super(ProductFormBase, self).configure(request, instance, edit)

        # meta preview control
        self.fields['_meta_preview'].widget = MetaPreviewWidget(
            attrs={
                'class': 'no-label',
                'path': request.path_info,
                'form': self
            })

        # excerpt
        self.fields[
            '_excerpt'].help_text = 'Provide your elevator pitch to the customer (max. %d characters)' % settings.CMS_EXCERPT_LENGTH

        if request.settings.barcode_system and request.settings.sku_is_barcode:
            self.remove_field('sku')
            self.fields['barcode'].label = 'SKU / Barcode'
            self.fields[
                'barcode'].help_text = 'SKU / Barcode (%s)' % request.settings.barcode_system.upper(
                )

        # multiple categories
        if settings.SHOP_MULTIPLE_CATEGORIES:
            self.remove_field('category')
        else:
            self.remove_field('categories')

        # loan applications
        if settings.SHOP_LOAN_ENABLED:
            queryset = FinanceOption.objects.filter(
                enabled=True, per_product=True).order_by('seq')
            self.fields['finance_options'].queryset = queryset

            if queryset.count() == 0:
                self.remove_field('finance_options')
                self.update_sections()
        else:
            self.remove_field('finance_options')
            self.remove_field('loan_exempt')

        # SKU / inventory
        if not (instance and instance.sku_enabled):
            self.remove_tab('SKU / Inventory')

        self.update_sections()

    def clean_slug(self):
        slug = self.cleaned_data.get('slug')

        if slug:
            products = get_product_model().objects.filter(slug=slug)
            if self._edit and self._instance:
                products = products.exclude(pk=self._instance.pk)
            if products.count() > 0:
                raise forms.ValidationError(
                    'This slug is already used. Please choose a different slug.'
                )

        return slug

    def clean_excerpt(self):
        excerpt = self.cleaned_data.get('_excerpt')

        if excerpt:
            if len(excerpt) > settings.CMS_EXCERPT_LENGTH:
                raise forms.ValidationError(
                    'The maximum allowed length is %d characters.' %
                    settings.CMS_EXCERPT_LENGTH)

        return excerpt

    def clean_price(self):
        return clean_price(self, 'price', self.cleaned_data)

    def clean_rrp(self):
        return clean_price(self, 'rrp', self.cleaned_data)

    def clean_previous_price(self):
        return clean_price(self, 'previous_price', self.cleaned_data)

    def clean(self):
        d = super(ProductFormBase, self).clean()

        barcode_system = d.get('barcode_system')
        barcode = d.get('barcode')

        if barcode_system is None:
            barcode_system = self._request.settings.barcode_system

        # verify that the barcode is correct...
        if barcode and barcode_system:
            try:
                d['barcode'] = verify_barcode(barcode_system, barcode)
            except BarcodeError, e:
                self.field_error('barcode', e.msg)

        return d
Exemple #4
0
class InventoryForm(BaseModelForm, ProductSKUFormMixin):
    class Meta:
        model = ProductSKU
        fields = '__all__'
        widgets = {
            'price':
            BootstrapTextInput(prepend=settings.CURRENCY,
                               attrs={'class': 'input-medium'}),
        }
        sections = {'enabled': 'SKU', 'price': 'Price and Stock Level'}

    product = BrowseProductField()

    variety_options = ModelCollectionField(
        label='Variety Options',
        add_label='Add Variety Option',
        required=True,
        queryset=VarietyOption.objects.all(),
        url='/admin/variety-options/',
        title='Variety Options',
        model_title='Variety Option',
        viewmode=ModelCollectionField.VIEWMODE_LIST,
        allow_duplicates=False,
        sortable=False,
        help_text=
        'Add the number of unique variety options that this Stock Keeping Unit (SKU) represents.'
    )

    def configure(self, request, instance, edit):
        super(InventoryForm, self).configure(request, instance, edit)

        # barcode system
        try:
            product = instance.product
        except:
            product = None

        self._barcode_system = request.settings.get_barcode_system(product)

        if self._barcode_system:
            if request.settings.sku_is_barcode:
                self.remove_field('sku')
                self.fields['barcode'].label = 'SKU / Barcode'
                self.fields[
                    'barcode'].help_text = 'SKU / Barcode (%s)' % self._barcode_system.upper(
                    )
            else:
                self.fields[
                    'barcode'].help_text = 'Barcode (%s)' % self._barcode_system.upper(
                    )
        else:
            self.remove_field('barcode')

    def clean(self):
        """
        Verify that SKU is unique and valid.
        """
        d = super(InventoryForm, self).clean()

        product_model = get_product_model()

        # SKU
        sku = d.get('sku')
        if sku:
            # SKU must be unique across all product SKUs
            x = ProductSKU.objects.filter(sku=sku)
            if self._edit: x = x.exclude(pk=self._instance.pk)
            if x.count() > 0:
                self.field_error('sku', 'This SKU number already exists.')

            # SKU cannot match any product-specific SKU
            products = product_model.objects.filter(sku=sku)
            if products.count() > 0:
                self.field_error(
                    'sku', 'This SKU number already exists for product: %s' %
                    products[0])

        # barcode
        barcode = d.get('barcode')
        if barcode:
            # barcode must be unique across all product SKUs
            x = ProductSKU.objects.filter(barcode=barcode)
            if self._edit: x = x.exclude(pk=self._instance.pk)
            if x.count() > 0:
                self.field_error('barcode',
                                 'This barcode number already exists.')

            # barcode cannot match any product-specific barcode
            products = product_model.objects.filter(barcode=barcode)
            if products.count() > 0:
                self.field_error(
                    'barcode',
                    'This barcode number already exists for product: %s' %
                    products[0])

        product = d.get('product')
        if product:
            # product must be SKU enabled
            if not product.sku_enabled:
                self.field_error(
                    'product', 'This product is not enabled for SKU numbers.')

            variety_options = d.get('variety_options')
            if variety_options:
                # all varieties must be enabled for SKU usage
                for variety_option in variety_options:
                    if not variety_option.variety.sku:
                        self.field_error(
                            'variety_options',
                            'The variety \'%s\' is not enabled for SKU numbers (\'%s\').'
                            % (variety_option.variety, variety_option))

                # all variety options must have a different variety. We cannot
                # map to multiple options of the same variety...
                variety_ids = [option.variety.pk for option in variety_options]
                if len(set(variety_ids)) != len(variety_ids):
                    self.field_error(
                        'variety_options',
                        'A SKU number must be a unique combination of varieties and cannot map to multiple options of the same variety.'
                    )

                # combination of variety options must match all required
                # varieties
                x = ProductSKU.objects.filter(product=product, enabled=True)
                if self._edit: x = x.exclude(pk=self._instance.pk)
                if x.count() > 0:
                    # all required varieties must be assigned...
                    required_varieties = [
                        variety_option.variety
                        for variety_option in x[0].variety_options.all()
                    ]
                    required_variety_ids = [v.pk for v in required_varieties]
                    varieties = [option.variety for option in variety_options]
                    variety_ids = [v.pk for v in varieties]
                    for required_variety in required_varieties:
                        if required_variety.pk not in variety_ids:
                            self.field_error(
                                'variety_options',
                                'This SKU number must map to the required variety: \'%s\'.'
                                % (required_variety))

                    # we cannot have a variety that is not allowed...
                    for variety_option in variety_options:
                        if variety_option.variety.pk not in required_variety_ids:
                            self.field_error(
                                'variety_options',
                                'Variety option \'%s\' does not belong to \'%s\'.'
                                % (variety_option, ' or '.join(
                                    ['%s' % v for v in required_varieties])))

                # combination of variety options must be unique for this product
                x = ProductSKU.objects.filter(product=product, enabled=True)
                for variety_option in variety_options:
                    x = x.filter(variety_options=variety_option)
                if self._edit: x = x.exclude(pk=self._instance.pk)
                if x.count() > 0:
                    self.field_error(
                        'variety_options',
                        'This combination of variety options already exists.')

        return d
Exemple #5
0
class PageFormBase(BaseModelForm):
    """
    Form for editing CMS pages. This is the base form class for cms pages and
    cms entities. However, when deriving new forms, use PageForm or EntityForm.
    """
    class Meta:
        exclude = ['_nav', '_data']
        widgets = {
            'title':
            forms.TextInput(attrs={
                'class': 'slugify',
                'autocomplete': 'off'
            }),
            'slug':
            forms.TextInput(attrs={
                'class': 'slug',
                'autocomplete': 'off'
            }),
            'seq':
            NumberInput(attrs={
                'class': 'input-mini',
                'min': '0'
            }),
            '_excerpt':
            widgets.Textarea(attrs={'rows': '8'}),
            '_meta_description':
            widgets.Textarea()
        }
        tabs = [{
            'title': 'Content',
            'fields': []
        }, {
            'title':
            'Title',
            'fields': [
                'title',
                'slug',
                'legacy_url',
                '_excerpt',
                '_meta_title',
                '_meta_description',
                '_meta_keywords',
                '_meta_preview',
            ]
        }, {
            'title': 'Presentation',
            'fields': [
                'template',
            ]
        }, {
            'title': 'Gallery',
            'fields': ['image', '_gallery_images']
        }, {
            'title': 'Visibility',
            'fields': ['disabled', 'sitemap', 'seq']
        }]
        sections = {
            'title': 'Page Data',
            '_excerpt': 'Excerpt',
            '_meta_title': 'Meta Data',
            '_meta_preview': 'Search Result Preview',
            'template': 'Template',
            'image': 'Primary Image and Gallery'
        }
        limits = {
            '_meta_title': FormInputLimit(65),
            '_meta_description': FormInputLimit(240)
        }

    image = BrowseImagesField(required=False)

    _gallery_images = ModelCollectionField(
        label='Image Gallery',
        required=False,
        queryset=Media.objects.filter(is_image=True),
        url='/admin/images/',
        title='Gallery',
        model_title='Images',
        help_text='Add an arbitrarily number of images to this page.')

    _meta_preview = fields.Field(
        label=None,
        required=False,
        help_text='This preview is for demonstration purposes only ' + \
                  'and the actual search result may differ from the preview.'
    )

    parent = BrowsePagesField(
        required=False,
        help_text='Select the parent page for this page for the purpose of ' + \
                  'presenting multi-level navigation.'
    )

    def clean_legacy_url(self):
        """
        Allow a full domain name to be copy into the legacy_url field and
        have the absolute path extracted automatically.
        """
        legacy_url = self.cleaned_data.get('legacy_url')

        if legacy_url:
            legacy_url = to_legacy_url(legacy_url)

        return legacy_url

    def clean(self):
        """
        Detect navigation changes
        """
        cleaned_data = super(PageFormBase, self).clean()

        if self._instance and self._instance.pk:
            nav_changed = (self.cleaned_data.get('title') != getattr(
                self._instance, 'title', None)
                           or self.cleaned_data.get('navigation_title') !=
                           getattr(self._instance, 'navigation_title', None)
                           or self.cleaned_data.get('slug') != getattr(
                               self._instance, 'slug', None))
            if nav_changed:
                self._instance.nav_updated_on = datetime.now()

        return cleaned_data

    def configure(self, request, instance, edit):
        """
        Configure form
        """
        super(PageFormBase, self).configure(request, instance, edit)
        from cubane.cms.templatetags.cms_tags import rewrite_image_references

        # create textarea field for each slot.
        if self.is_tabbed:
            for slotname in settings.CMS_SLOTNAMES:
                fieldname = 'slot_%s' % slotname
                self.fields[fieldname] = forms.CharField(
                    required=False,
                    widget=forms.Textarea(
                        attrs={
                            'class':
                            'editable-html preview no-label full-height',
                            'data-slotname': slotname
                        }))
                self._tabs[0]['fields'].append(fieldname)

        # load initial content for each slot.
        if edit or self.is_duplicate:
            for slotname in settings.CMS_SLOTNAMES:
                fieldname = 'slot_%s' % slotname
                self.fields[fieldname].initial = \
                    rewrite_image_references(instance.get_slot_content(slotname))

        self.fields['_meta_preview'].widget = MetaPreviewWidget(
            attrs={
                'class': 'no-label',
                'path': request.path_info,
                'form': self
            })
Exemple #6
0
class TestModelCollectionFieldForm(BaseForm):
    pages = ModelCollectionField(queryset=Page.objects.all())