Import and export admin panel added using product_db code.

1. cd  ..lib/python2.7/site-packages/cartridge/shop/templates/admin
2. mkdir import_export
3. copy the following attached files to above directory
       3.1. base.html
       3.2 change_list.html
       3.3 change_list_import_export.html
       3.4 export.html
       3.5 import.html
4.  replace ..lib/python2.7/site-packages/cartridge/shop/admin.py with the 
attached admin.py file.

5. replace ..lib/python2.7/site-packages/cartridge/shop/forms.py with the 
attached forms.py file.


6. python manage.py runserver

7. go to http://127.0.0.1:8000/admin/shop/product/

8. Find import and export as part of admin panel

9. Import require parent dir of images to be copied for the product.
10. So place product images in the above directory before performing an 
import.

-- 
You received this message because you are subscribed to the Google Groups 
"Mezzanine Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.
from __future__ import unicode_literals
from future.builtins import super, zip

"""
Admin classes for all the shop models.

Many attributes in here are controlled by the ``SHOP_USE_VARIATIONS``
setting which defaults to True. In this case, variations are managed in
the product change view, and are created given the ``ProductOption``
values selected.

A handful of fields (mostly those defined on the abstract ``Priced``
model) are duplicated across both the ``Product`` and
``ProductVariation`` models, with the latter being the definitive
source, and the former supporting denormalised data that can be
referenced when iterating through products, without having to
query the underlying variations.

When ``SHOP_USE_VARIATIONS`` is set to False, a single variation is
still stored against each product, to keep consistent with the overall
model design. Since from a user perspective there are no variations,
the inlines for variations provide a single inline for managing the
one variation per product, so in the product change view, a single set
of price fields are available via the one variation inline.

Also when ``SHOP_USE_VARIATIONS`` is set to False, the denormalised
price fields on the product model are presented as editable fields in
the product change list - if these form fields are used, the values
are then pushed back onto the one variation for the product.
"""

from copy import deepcopy
import csv
import os
import shutil
import sys
from datetime import datetime

import django
from django.conf.urls import url
from django.contrib import admin
from django.contrib import messages
from django.contrib.admin.templatetags.admin_static import static
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.core.urlresolvers import reverse
from django.db.models import ImageField
from django.http import HttpResponseRedirect, HttpResponse
from django.utils.translation import ugettext_lazy as _
from django.template.response import TemplateResponse


from mezzanine.conf import settings
from mezzanine.core.admin import (DisplayableAdmin,
                                  TabularDynamicInlineAdmin,
                                  BaseTranslationModelAdmin)
from mezzanine.core.models import CONTENT_STATUS_PUBLISHED
from mezzanine.pages.admin import PageAdmin

from cartridge.shop.fields import MoneyField
from cartridge.shop.forms import ProductAdminForm, ProductVariationAdminForm
from cartridge.shop.forms import ProductVariationAdminFormset
from cartridge.shop.forms import DiscountAdminForm, ImageWidget, MoneyWidget
from cartridge.shop.models import Category, Product, ProductImage
from cartridge.shop.models import ProductVariation, ProductOption, Order
from cartridge.shop.models import OrderItem, Sale, DiscountCode
from cartridge.shop.views import HAS_PDF

from .forms import ExportForm, ImportForm

# images get copied from thie directory
#LOCAL_IMAGE_DIR = "/tmp/orig"
# images get copied to this directory under STATIC_ROOT
IMAGE_SUFFIXES = [".jpg", ".png", ".JPG", ".jpeg", ".JPEG", ".tif", ".gif", ".GIF"]
EMPTY_IMAGE_ENTRIES = ["Please add", "N/A", ""]
DATE_FORMAT = "%Y-%m-%d"
TIME_FORMAT = "%H:%M"

# Here we define what column headings are used in the csv.
TITLE = _("Title")
CONTENT = _("Content")
DESCRIPTION = _("Description")
SKU = _("SKU")
IMAGE = _("Image")
CATEGORY = _("Category")
SUB_CATEGORY = _("Sub-Category")
# SIZE = _("Size")
NUM_IN_STOCK = _("Number in Stock")
UNIT_PRICE = _("Unit Price")
SALE_PRICE = _("Sale Price")
SALE_START_DATE = _("Sale Start Date")
SALE_START_TIME = _("Sale Start Time")
SALE_END_DATE = _("Sale End Date")
SALE_END_TIME = _("Sale End Time")

DATETIME_FORMAT = "%s %s" % (DATE_FORMAT, TIME_FORMAT)
SITE_MEDIA_IMAGE_DIR = _("product")
SITE_MEDIA_IMAGE_NDIR = _("media/product")
PRODUCT_IMAGE_DIR = os.path.join(str(settings.STATIC_ROOT), str(SITE_MEDIA_IMAGE_NDIR))
TYPE_CHOICES = dict()
for id, choice in settings.SHOP_OPTION_TYPE_CHOICES:
    TYPE_CHOICES[choice] = id

fieldnames = [TITLE, CONTENT, DESCRIPTION, CATEGORY, SUB_CATEGORY,
    SKU, IMAGE, NUM_IN_STOCK, UNIT_PRICE,
    SALE_PRICE, SALE_START_DATE, SALE_START_TIME, SALE_END_DATE, SALE_END_TIME]
# TODO: Make sure no options conflict with other fieldnames.
fieldnames += TYPE_CHOICES.keys()

# Lists of field names.
option_fields = [f.name for f in ProductVariation.option_fields()]
_flds = lambda s: [f.name for f in Order._meta.fields if f.name.startswith(s)]
billing_fields = _flds("billing_detail")
shipping_fields = _flds("shipping_detail")


################
#  CATEGORIES  #
################

# Categories fieldsets are extended from Page fieldsets, since
# categories are a Mezzanine Page type.
category_fieldsets = deepcopy(PageAdmin.fieldsets)
category_fieldsets[0][1]["fields"][3:3] = ["content", "products"]
category_fieldsets += ((_("Product filters"), {
    "fields": ("sale", ("price_min", "price_max"), "combined"),
    "classes": ("collapse-closed",)},),)
if settings.SHOP_CATEGORY_USE_FEATURED_IMAGE:
    category_fieldsets[0][1]["fields"].insert(3, "featured_image")

# Options are only used when variations are in use, so only provide
# them as filters for dynamic categories when this is the case.
if settings.SHOP_USE_VARIATIONS:
    category_fieldsets[-1][1]["fields"] = (("options",) +
                                        category_fieldsets[-1][1]["fields"])


class CategoryAdmin(PageAdmin):
    fieldsets = category_fieldsets
    formfield_overrides = {ImageField: {"widget": ImageWidget}}
    filter_horizontal = ("options", "products",)

################
#  VARIATIONS  #
################

# If variations aren't used, the variation inline should always
# provide a single inline for managing the single variation per
# product.
variation_fields = ["sku", "num_in_stock", "unit_price",
                    "sale_price", "sale_from", "sale_to", "image"]
if settings.SHOP_USE_VARIATIONS:
    variation_fields.insert(1, "default")
    variations_max_num = None
    variations_extra = 0
else:
    variations_max_num = 1
    variations_extra = 1


class ProductVariationAdmin(admin.TabularInline):
    verbose_name_plural = _("Current variations")
    model = ProductVariation
    fields = variation_fields
    max_num = variations_max_num
    extra = variations_extra
    formfield_overrides = {MoneyField: {"widget": MoneyWidget}}
    form = ProductVariationAdminForm
    formset = ProductVariationAdminFormset
    ordering = ["option%s" % i for i in settings.SHOP_OPTION_ADMIN_ORDER]


class ProductImageAdmin(TabularDynamicInlineAdmin):
    model = ProductImage
    formfield_overrides = {ImageField: {"widget": ImageWidget}}

##############
#  PRODUCTS  #
##############

product_fieldsets = deepcopy(DisplayableAdmin.fieldsets)
product_fieldsets[0][1]["fields"].insert(2, "available")
product_fieldsets[0][1]["fields"].extend(["content", "categories"])
product_fieldsets = list(product_fieldsets)

other_product_fields = []
if settings.SHOP_USE_RELATED_PRODUCTS:
    other_product_fields.append("related_products")
if settings.SHOP_USE_UPSELL_PRODUCTS:
    other_product_fields.append("upsell_products")
if len(other_product_fields) > 0:
    product_fieldsets.append((_("Other products"), {
        "classes": ("collapse-closed",),
        "fields": tuple(other_product_fields)}))

product_list_display = ["admin_thumb", "title", "status", "available",
                        "admin_link"]
product_list_editable = ["status", "available"]

# If variations are used, set up the product option fields for managing
# variations. If not, expose the denormalised price fields for a product
# in the change list view.
if settings.SHOP_USE_VARIATIONS:
    product_fieldsets.insert(1, (_("Create new variations"),
        {"classes": ("create-variations",), "fields": option_fields}))
else:
    extra_list_fields = ["sku", "unit_price", "sale_price", "num_in_stock"]
    product_list_display[4:4] = extra_list_fields
    product_list_editable.extend(extra_list_fields)


class ProductAdmin(DisplayableAdmin):

    class Media:
        js = (static("cartridge/js/admin/product_variations.js"),)
        css = {"all": (static("cartridge/css/admin/product.css"),)}

    list_display = product_list_display
    list_display_links = ("admin_thumb", "title")
    list_editable = product_list_editable
    list_filter = ("status", "available", "categories")
    filter_horizontal = ("categories",) + tuple(other_product_fields)
    search_fields = ("title", "content", "categories__title",
                     "variations__sku")
    inlines = (ProductImageAdmin, ProductVariationAdmin)
    form = ProductAdminForm
    fieldsets = product_fieldsets
    
    #: template for export view
    export_template_name = 'admin/import_export/export.html'
    #: template for change_list view
    change_list_template = 'admin/import_export/change_list_import_export.html'
    #: template for import view
    import_template_name = 'admin/import_export/import.html'

    def get_model_info(self):
        # module_name is renamed to model_name in Django 1.8
        app_label = self.model._meta.app_label
        try:
            return (app_label, self.model._meta.model_name,)
        except AttributeError:
            return (app_label, self.model._meta.module_name,)

    def get_urls(self):
        info = self.get_model_info()
        urls = super(ProductAdmin, self).get_urls()
        my_urls = [
            url(r'^import/$',
                self.admin_site.admin_view(self.import_action),
                name='%s_%s_import' % info),
            url(r'^export/$',
                self.admin_site.admin_view(self.export_action),
                name='%s_%s_export' % self.get_model_info()),
        ]
        return my_urls + urls

    def _product_from_row(self, row):
        product, created = Product.objects.get_or_create(title=row[TITLE])
        product.content = row[CONTENT]
        product.description = row[DESCRIPTION]
        # TODO: set the 2 below from spreadsheet.
        product.status = CONTENT_STATUS_PUBLISHED
        product.available = True
        # TODO: allow arbitrary level/number of categories.
        base_cat, created = Category.objects.get_or_create(title=row[CATEGORY])
        if row[SUB_CATEGORY]:
            sub_cat, created = Category.objects.get_or_create(
                title=row[SUB_CATEGORY],
                parent=base_cat)
            product.categories.add(sub_cat)
        shop_cat, created = Category.objects.get_or_create(title="Shop")
        product.categories.add(shop_cat)
        return product


    def _make_image(self, image_str, product, local_img_dir):
        if image_str in EMPTY_IMAGE_ENTRIES:
            return None
        # try adding various image suffixes, if none given in original filename.
        root, suffix = os.path.splitext(image_str)
        if suffix not in IMAGE_SUFFIXES:
            raise CommandError("INCORRECT SUFFIX: %s" % image_str)
        image_path = os.path.join(local_img_dir , image_str)
        if not os.path.exists(image_path):
            raise CommandError("NO FILE %s" % image_path)
        shutil.copy(image_path, PRODUCT_IMAGE_DIR)
        # shutil.copy(image_path, os.path.join(PRODUCT_IMAGE_DIR, "orig"))
        image, created = ProductImage.objects.get_or_create(
            file="%s" % (image_str),
            description=image_str,  # TODO: handle column for this.
            product=product)
        return image


    def _make_date(self, date_str, time_str):
        date_string = '%s %s' % (date_str, time_str)
        date = datetime.datetime.strptime(date_string, DATETIME_FORMAT)
        return date

    def import_action(self, request, *args, **kwargs):
        '''
        Perform a dry_run of the import to make sure the import will not
        result in errors.  If there where no error, save the user
        uploaded file to a local temp file that will be used by
        'process_import' for the actual import.
        '''
        print("############################PROCESS IMPORT ACTION#################")
        context = {}
        import_formats = ["csv",]
        form = ImportForm(import_formats,
                          request.POST or None,
                          request.FILES or None)
        if request.POST and form.is_valid():
            print("############IMPORTING ######################")
            input_format = ["csv",]
            import_file = form.cleaned_data['import_file']
            local_img_dir = form.cleaned_data['imgs_dirname']
            print(_("Importing .."))
            # More appropriate for testing.
            # Product.objects.all().delete()
            #reader = csv.DictReader(open(import_file))
            reader = csv.DictReader(import_file)
            for row in reader:
                print(row)
                product = self._product_from_row(row)
                try:
                    variation = ProductVariation.objects.create(
                        # strip whitespace
                        sku=row[SKU].replace(" ", ""),
                        product=product,
                    )
                except IntegrityError:
                    raise CommandError("Product with SKU exists! sku: %s" % row[SKU])
                if row[NUM_IN_STOCK]:
                    variation.num_in_stock = row[NUM_IN_STOCK]
                if row[UNIT_PRICE]:
                    variation.unit_price = row[UNIT_PRICE]
                if row[SALE_PRICE]:
                    variation.sale_price = row[SALE_PRICE]
                if row[SALE_START_DATE] and row[SALE_START_TIME]:
                    variation.sale_from = self._make_date(row[SALE_START_DATE],
                                                        row[SALE_START_TIME])
                if row[SALE_END_DATE] and row[SALE_END_TIME]:
                    variation.sale_to = self._make_date(row[SALE_END_DATE],
                                                        row[SALE_END_TIME])
                for option in TYPE_CHOICES:
                    if row[option]:
                        name = "option%s" % TYPE_CHOICES[option]
                        setattr(variation, name, row[option])
                        new_option, created = ProductOption.objects.get_or_create(
                            type=TYPE_CHOICES[option],  # TODO: set dynamically
                            name=row[option])
                variation.save()
                image = self._make_image(row[IMAGE], product, local_img_dir )
                if image:
                    variation.image = image
                product.variations.manage_empty()
                product.variations.set_default_images([])
                product.copy_default_variation()
                product.save()

            print("Variations: %s" % ProductVariation.objects.all().count())
            print("Products: %s" % Product.objects.all().count())
            #context['confirm_form'] = ConfirmImportForm(initial={
            #    'import_file_name': import_file.name,
            #    'original_file_name': import_file.name,
            #    'input_format': form.cleaned_data['input_format'],
            #})
            print("############################PROCESS IMPORT FINISHED #################")
            success_message = u'Import finished' 
            messages.success(request, success_message)
            #tmp_storage.remove()

            url = reverse('admin:%s_%s_changelist' % self.get_model_info(),
                          current_app=self.admin_site.name)
            return HttpResponseRedirect(url)

        if django.VERSION >= (1, 8, 0):
            context.update(self.admin_site.each_context(request))
        elif django.VERSION >= (1, 7, 0):
            context.update(self.admin_site.each_context())

        context['form'] = form
        context['opts'] = self.model._meta
        #context['fields'] = [f.column_name for f in resource.get_fields()]

        request.current_app = self.admin_site.name
        return TemplateResponse(request, [self.import_template_name],
                                context)

    def export_action(self, request, *args, **kwargs):
        '''
        Perform the actual export action (after the user has confirmed he
        wishes to import)
        '''
        print("########################## EXPORT ACTION #######################################")
        formats=("csv",)
        form = ExportForm(formats, request.POST or None)
        if form.is_valid():
            print(_("Exporting .."))
            #response = HttpResponse(content_type='text/csv')
            #response['Content-Disposition'] = 'attachment; filename=hostedCustomerReport.csv'
            filehandle = open("/tmp/tmpexport.csv", 'w')
            writer = csv.DictWriter(filehandle, fieldnames=fieldnames)
            #writer = csv.writer(response)
            headers = dict()
            for field in fieldnames:
                headers[field] = field
            writer.writerow(headers)
            for pv in ProductVariation.objects.all():
                row = dict()
                row[TITLE] = pv.product.title
                row[CONTENT] = pv.product.content
                row[DESCRIPTION] = pv.product.description
                row[SKU] = pv.sku
                row[IMAGE] = pv.image
                # TODO: handle multiple categories, and multiple levels of categories
                cat = pv.product.categories.all()[0]
                if cat.parent:
                    row[SUB_CATEGORY] = cat.title
                    row[CATEGORY] = cat.parent.title
                else:
                    row[CATEGORY] = cat.title
                    row[SUB_CATEGORY] = ""

                for option in TYPE_CHOICES:
                    row[option] = getattr(pv, "option%s" % TYPE_CHOICES[option])

                row[NUM_IN_STOCK] = pv.num_in_stock
                row[UNIT_PRICE] = pv.unit_price
                row[SALE_PRICE] = pv.sale_price
                try:
                    row[SALE_START_DATE] = pv.sale_from.strftime(DATE_FORMAT)
                    row[SALE_START_TIME] = pv.sale_from.strftime(TIME_FORMAT)
                except AttributeError:
                    pass
                try:
                    row[SALE_END_DATE] = pv.sale_to.strftime(DATE_FORMAT)
                    row[SALE_END_TIME] = pv.sale_to.strftime(TIME_FORMAT)
                except AttributeError:
                    pass
                writer.writerow(row)
            filehandle.close()
            f = open('/tmp/tmpexport.csv', 'r')
            response = HttpResponse(f, content_type='text/csv')
            response['Content-Disposition'] = 'attachment; filename=product-info.csv'
            return response

        context = {}

        if django.VERSION >= (1, 8, 0):
            context.update(self.admin_site.each_context(request))
        elif django.VERSION >= (1, 7, 0):
            context.update(self.admin_site.each_context())

        context['form'] = form
        context['opts'] = self.model._meta
        request.current_app = self.admin_site.name
        return TemplateResponse(request, [self.export_template_name],
                                context)

    def save_model(self, request, obj, form, change):
        """
        Store the product object for creating variations in save_formset.
        """
        super(ProductAdmin, self).save_model(request, obj, form, change)
        self._product = obj

    def save_formset(self, request, form, formset, change):
        """

        Here be dragons. We want to perform these steps sequentially:

        - Save variations formset
        - Run the required variation manager methods:
          (create_from_options, manage_empty, etc)
        - Save the images formset

        The variations formset needs to be saved first for the manager
        methods to have access to the correct variations. The images
        formset needs to be run last, because if images are deleted
        that are selected for variations, the variations formset will
        raise errors when saving due to invalid image selections. This
        gets addressed in the set_default_images method.

        An additional problem is the actual ordering of the inlines,
        which are in the reverse order for achieving the above. To
        address this, we store the images formset as an attribute, and
        then call save on it after the other required steps have
        occurred.

        """

        # Store the images formset for later saving, otherwise save the
        # formset.
        if formset.model == ProductImage:
            self._images_formset = formset
        else:
            super(ProductAdmin, self).save_formset(request, form, formset,
                                                   change)

        # Run each of the variation manager methods if we're saving
        # the variations formset.
        if formset.model == ProductVariation:

            # Build up selected options for new variations.
            options = dict([(f, request.POST.getlist(f)) for f in option_fields
                             if request.POST.getlist(f)])
            # Create a list of image IDs that have been marked to delete.
            deleted_images = [request.POST.get(f.replace("-DELETE", "-id"))
                for f in request.POST
                if f.startswith("images-") and f.endswith("-DELETE")]

            # Create new variations for selected options.
            self._product.variations.create_from_options(options)
            # Create a default variation if there are none.
            self._product.variations.manage_empty()

            # Remove any images deleted just now from variations they're
            # assigned to, and set an image for any variations without one.
            self._product.variations.set_default_images(deleted_images)

            # Save the images formset stored previously.
            super(ProductAdmin, self).save_formset(request, form,
                                                 self._images_formset, change)

            # Run again to allow for no images existing previously, with
            # new images added which can be used as defaults for variations.
            self._product.variations.set_default_images(deleted_images)

            # Copy duplicate fields (``Priced`` fields) from the default
            # variation to the product.
            self._product.copy_default_variation()

            # Save every translated fields from ``ProductOption`` into
            # the required ``ProductVariation``
            if settings.USE_MODELTRANSLATION:
                from collections import OrderedDict
                from modeltranslation.utils import (build_localized_fieldname
                                                    as _loc)
                for opt_name in options:
                    for opt_value in options[opt_name]:
                        opt_obj = ProductOption.objects.get(type=opt_name[6:],
                                                            name=opt_value)
                        params = {opt_name: opt_value}
                        for var in self._product.variations.filter(**params):
                            for code in OrderedDict(settings.LANGUAGES):
                                setattr(var, _loc(opt_name, code),
                                        getattr(opt_obj, _loc('name', code)))
                            var.save()


class ProductOptionAdmin(BaseTranslationModelAdmin):
    ordering = ("type", "name")
    list_display = ("type", "name")
    list_display_links = ("type",)
    list_editable = ("name",)
    list_filter = ("type",)
    search_fields = ("type", "name")
    radio_fields = {"type": admin.HORIZONTAL}


class OrderItemInline(admin.TabularInline):
    verbose_name_plural = _("Items")
    model = OrderItem
    extra = 0
    formfield_overrides = {MoneyField: {"widget": MoneyWidget}}


def address_pairs(fields):
    """
    Zips address fields into pairs, appending the last field if the
    total is an odd number.
    """
    pairs = list(zip(fields[::2], fields[1::2]))
    if len(fields) % 2:
        pairs.append(fields[-1])
    return pairs


order_list_display = ("id", "billing_name", "total", "time", "status",
                      "transaction_id")
if HAS_PDF:
    order_list_display += ("invoice",)


class OrderAdmin(admin.ModelAdmin):

    class Media:
        css = {"all": (static("cartridge/css/admin/order.css"),)}

    ordering = ("status", "-id")
    list_display = order_list_display
    list_editable = ("status",)
    list_filter = ("status", "time")
    list_display_links = ("id", "billing_name",)
    search_fields = (["id", "status", "transaction_id"] +
                     billing_fields + shipping_fields)
    date_hierarchy = "time"
    radio_fields = {"status": admin.HORIZONTAL}
    inlines = (OrderItemInline,)
    formfield_overrides = {MoneyField: {"widget": MoneyWidget}}
    fieldsets = (
        (_("Billing details"), {"fields": address_pairs(billing_fields)}),
         (_("Shipping details"), {"fields": address_pairs(shipping_fields)}),
        (None, {"fields": ("additional_instructions", ("shipping_total",
            "shipping_type"), ('tax_total', 'tax_type'),
             ("discount_total", "discount_code"), "item_total",
            ("total", "status"), "transaction_id")}),
    )

    def change_view(self, *args, **kwargs):
        if kwargs.get("extra_context", None) is None:
            kwargs["extra_context"] = {}
        kwargs["extra_context"]["has_pdf"] = HAS_PDF
        return super(OrderAdmin, self).change_view(*args, **kwargs)


class SaleAdmin(admin.ModelAdmin):
    list_display = ("title", "active", "discount_deduct", "discount_percent",
        "discount_exact", "valid_from", "valid_to")
    list_editable = ("active", "discount_deduct", "discount_percent",
        "discount_exact", "valid_from", "valid_to")
    filter_horizontal = ("categories", "products")
    formfield_overrides = {MoneyField: {"widget": MoneyWidget}}
    form = DiscountAdminForm
    fieldsets = (
        (None, {"fields": ("title", "active")}),
        (_("Apply to product and/or products in categories"),
            {"fields": ("products", "categories")}),
        (_("Reduce unit price by"),
            {"fields": (("discount_deduct", "discount_percent",
            "discount_exact"),)}),
        (_("Sale period"), {"fields": (("valid_from", "valid_to"),)}),
    )


class DiscountCodeAdmin(admin.ModelAdmin):
    list_display = ("title", "active", "code", "discount_deduct",
        "discount_percent", "min_purchase", "free_shipping", "valid_from",
        "valid_to")
    list_editable = ("active", "code", "discount_deduct", "discount_percent",
        "min_purchase", "free_shipping", "valid_from", "valid_to")
    filter_horizontal = ("categories", "products")
    formfield_overrides = {MoneyField: {"widget": MoneyWidget}}
    form = DiscountAdminForm
    fieldsets = (
        (None, {"fields": ("title", "active", "code")}),
        (_("Apply to product and/or products in categories"),
            {"fields": ("products", "categories")}),
        (_("Reduce unit price by"),
            {"fields": (("discount_deduct", "discount_percent"),)}),
        (None, {"fields": (("min_purchase", "free_shipping"),)}),
        (_("Valid for"),
            {"fields": (("valid_from", "valid_to", "uses_remaining"),)}),
    )


admin.site.register(Category, CategoryAdmin)
admin.site.register(Product, ProductAdmin)
if settings.SHOP_USE_VARIATIONS:
    admin.site.register(ProductOption, ProductOptionAdmin)
admin.site.register(Order, OrderAdmin)
admin.site.register(Sale, SaleAdmin)
admin.site.register(DiscountCode, DiscountCodeAdmin)
*** admin.py.orig	2016-05-14 12:33:18.040113927 +0530
--- admin.py	2016-05-14 14:00:35.958529540 +0530
***************
*** 30,45 ****
--- 30,60 ----
  """
  
  from copy import deepcopy
+ import csv
+ import os
+ import shutil
+ import sys
+ from datetime import datetime
  
+ import django
+ from django.conf.urls import url
  from django.contrib import admin
  from django.contrib.admin.templatetags.admin_static import static
+ from django.core.management.base import BaseCommand
+ from django.core.management.base import CommandError
+ from django.contrib import messages
+ from django.core.urlresolvers import reverse
  from django.db.models import ImageField
+ from django.http import HttpResponseRedirect, HttpResponse
  from django.utils.translation import ugettext_lazy as _
+ from django.template.response import TemplateResponse
+ 
  
  from mezzanine.conf import settings
  from mezzanine.core.admin import (DisplayableAdmin,
                                    TabularDynamicInlineAdmin,
                                    BaseTranslationModelAdmin)
+ from mezzanine.core.models import CONTENT_STATUS_PUBLISHED
  from mezzanine.pages.admin import PageAdmin
  
  from cartridge.shop.fields import MoneyField
***************
*** 51,63 ****
  from cartridge.shop.models import OrderItem, Sale, DiscountCode
  from cartridge.shop.views import HAS_PDF
  
  
! # Lists of field names.
! option_fields = [f.name for f in ProductVariation.option_fields()]
! _flds = lambda s: [f.name for f in Order._meta.fields if f.name.startswith(s)]
! billing_fields = _flds("billing_detail")
! shipping_fields = _flds("shipping_detail")
! 
  
  ################
  #  CATEGORIES  #
--- 66,112 ----
  from cartridge.shop.models import OrderItem, Sale, DiscountCode
  from cartridge.shop.views import HAS_PDF
  
+ from .forms import ExportForm, ImportForm
  
! #PRODUCT_DB VARIABLES
! # images get copied from thie directory
! #LOCAL_IMAGE_DIR = "/tmp/orig"
! # images get copied to this directory under STATIC_ROOT
! IMAGE_SUFFIXES = [".jpg", ".png", ".JPG", ".jpeg", ".JPEG", ".tif", ".gif", ".GIF"]
! EMPTY_IMAGE_ENTRIES = ["Please add", "N/A", ""]
! DATE_FORMAT = "%Y-%m-%d"
! TIME_FORMAT = "%H:%M"
! 
! # Here we define what column headings are used in the csv.
! TITLE = _("Title")
! CONTENT = _("Content")
! DESCRIPTION = _("Description")
! SKU = _("SKU")
! IMAGE = _("Image")
! CATEGORY = _("Category")
! SUB_CATEGORY = _("Sub-Category")
! # SIZE = _("Size")
! NUM_IN_STOCK = _("Number in Stock")
! UNIT_PRICE = _("Unit Price")
! SALE_PRICE = _("Sale Price")
! SALE_START_DATE = _("Sale Start Date")
! SALE_START_TIME = _("Sale Start Time")
! SALE_END_DATE = _("Sale End Date")
! SALE_END_TIME = _("Sale End Time")
! 
! DATETIME_FORMAT = "%s %s" % (DATE_FORMAT, TIME_FORMAT)
! SITE_MEDIA_IMAGE_DIR = _("product")
! SITE_MEDIA_IMAGE_NDIR = _("media/product")
! PRODUCT_IMAGE_DIR = os.path.join(str(settings.STATIC_ROOT), str(SITE_MEDIA_IMAGE_NDIR))
! TYPE_CHOICES = dict()
! for id, choice in settings.SHOP_OPTION_TYPE_CHOICES:
!     TYPE_CHOICES[choice] = id
! 
! fieldnames = [TITLE, CONTENT, DESCRIPTION, CATEGORY, SUB_CATEGORY,
!     SKU, IMAGE, NUM_IN_STOCK, UNIT_PRICE,
!     SALE_PRICE, SALE_START_DATE, SALE_START_TIME, SALE_END_DATE, SALE_END_TIME]
! # TODO: Make sure no options conflict with other fieldnames.
! fieldnames += TYPE_CHOICES.keys()
  
  ################
  #  CATEGORIES  #
***************
*** 171,176 ****
--- 220,448 ----
      form = ProductAdminForm
      fieldsets = product_fieldsets
  
+     #: template for export view
+     export_template_name = 'admin/import_export/export.html'
+     #: template for change_list view
+     change_list_template = 'admin/import_export/change_list_import_export.html'
+     #: template for import view
+     import_template_name = 'admin/import_export/import.html'
+ 
+     def get_model_info(self):
+         # module_name is renamed to model_name in Django 1.8
+         app_label = self.model._meta.app_label
+         try:
+             return (app_label, self.model._meta.model_name,)
+         except AttributeError:
+             return (app_label, self.model._meta.module_name,)
+ 
+     def get_urls(self):
+         info = self.get_model_info()
+         urls = super(ProductAdmin, self).get_urls()
+         my_urls = [
+             url(r'^import/$',
+                 self.admin_site.admin_view(self.import_action),
+                 name='%s_%s_import' % info),
+             url(r'^export/$',
+                 self.admin_site.admin_view(self.export_action),
+                 name='%s_%s_export' % self.get_model_info()),
+         ]
+         return my_urls + urls
+ 
+     def _product_from_row(self, row):
+         product, created = Product.objects.get_or_create(title=row[TITLE])
+         product.content = row[CONTENT]
+         product.description = row[DESCRIPTION]
+         # TODO: set the 2 below from spreadsheet.
+         product.status = CONTENT_STATUS_PUBLISHED
+         product.available = True
+         # TODO: allow arbitrary level/number of categories.
+         base_cat, created = Category.objects.get_or_create(title=row[CATEGORY])
+         if row[SUB_CATEGORY]:
+             sub_cat, created = Category.objects.get_or_create(
+                 title=row[SUB_CATEGORY],
+                 parent=base_cat)
+             product.categories.add(sub_cat)
+         shop_cat, created = Category.objects.get_or_create(title="Shop")
+         product.categories.add(shop_cat)
+         return product
+ 
+ 
+     def _make_image(self, image_str, product, local_img_dir):
+         if image_str in EMPTY_IMAGE_ENTRIES:
+             return None
+         # try adding various image suffixes, if none given in original filename.
+         root, suffix = os.path.splitext(image_str)
+         if suffix not in IMAGE_SUFFIXES:
+             raise CommandError("INCORRECT SUFFIX: %s" % image_str)
+         image_path = os.path.join(local_img_dir , image_str)
+         if not os.path.exists(image_path):
+             raise CommandError("NO FILE %s" % image_path)
+         shutil.copy(image_path, PRODUCT_IMAGE_DIR)
+         # shutil.copy(image_path, os.path.join(PRODUCT_IMAGE_DIR, "orig"))
+         image, created = ProductImage.objects.get_or_create(
+             file="%s" % (image_str),
+             description=image_str,  # TODO: handle column for this.
+             product=product)
+         return image
+ 
+ 
+     def _make_date(self, date_str, time_str):
+         date_string = '%s %s' % (date_str, time_str)
+         date = datetime.datetime.strptime(date_string, DATETIME_FORMAT)
+         return date
+ 
+     def import_action(self, request, *args, **kwargs):
+         '''
+         Perform a dry_run of the import to make sure the import will not
+         result in errors.  If there where no error, save the user
+         uploaded file to a local temp file that will be used by
+         'process_import' for the actual import.
+         '''
+         context = {}
+         import_formats = ["csv",]
+         form = ImportForm(import_formats,
+                           request.POST or None,
+                           request.FILES or None)
+         if request.POST and form.is_valid():
+             input_format = ["csv",]
+             import_file = form.cleaned_data['import_file']
+             local_img_dir = form.cleaned_data['imgs_dirname']
+             print(_("Importing .."))
+             # More appropriate for testing.
+             # Product.objects.all().delete()
+             #reader = csv.DictReader(open(import_file))
+             reader = csv.DictReader(import_file)
+             for row in reader:
+                 print(row)
+                 product = self._product_from_row(row)
+                 try:
+                     variation = ProductVariation.objects.create(
+                         # strip whitespace
+                         sku=row[SKU].replace(" ", ""),
+                         product=product,
+                     )
+                 except IntegrityError:
+                     raise CommandError("Product with SKU exists! sku: %s" % row[SKU])
+                 if row[NUM_IN_STOCK]:
+                     variation.num_in_stock = row[NUM_IN_STOCK]
+                 if row[UNIT_PRICE]:
+                     variation.unit_price = row[UNIT_PRICE]
+                 if row[SALE_PRICE]:
+                     variation.sale_price = row[SALE_PRICE]
+                 if row[SALE_START_DATE] and row[SALE_START_TIME]:
+                     variation.sale_from = self._make_date(row[SALE_START_DATE],
+                                                         row[SALE_START_TIME])
+                 if row[SALE_END_DATE] and row[SALE_END_TIME]:
+                     variation.sale_to = self._make_date(row[SALE_END_DATE],
+                                                         row[SALE_END_TIME])
+                 for option in TYPE_CHOICES:
+                     if row[option]:
+                         name = "option%s" % TYPE_CHOICES[option]
+                         setattr(variation, name, row[option])
+                         new_option, created = ProductOption.objects.get_or_create(
+                             type=TYPE_CHOICES[option],  # TODO: set dynamically
+                             name=row[option])
+                 variation.save()
+                 image = self._make_image(row[IMAGE], product, local_img_dir )
+                 if image:
+                     variation.image = image
+                 product.variations.manage_empty()
+                 product.variations.set_default_images([])
+                 product.copy_default_variation()
+                 product.save()
+ 
+             print("Variations: %s" % ProductVariation.objects.all().count())
+             print("Products: %s" % Product.objects.all().count())
+             success_message = u'Import finished' 
+             messages.success(request, success_message)
+ 
+             url = reverse('admin:%s_%s_changelist' % self.get_model_info(),
+                           current_app=self.admin_site.name)
+             return HttpResponseRedirect(url)
+ 
+         if django.VERSION >= (1, 8, 0):
+             context.update(self.admin_site.each_context(request))
+         elif django.VERSION >= (1, 7, 0):
+             context.update(self.admin_site.each_context())
+ 
+         context['form'] = form
+         context['opts'] = self.model._meta
+         #context['fields'] = [f.column_name for f in resource.get_fields()]
+ 
+         request.current_app = self.admin_site.name
+         return TemplateResponse(request, [self.import_template_name],
+                                 context)
+ 
+     def export_action(self, request, *args, **kwargs):
+         '''
+         Perform the actual export action (after the user has confirmed he
+         wishes to import)
+         '''
+         formats=("csv",)
+         form = ExportForm(formats, request.POST or None)
+         if form.is_valid():
+             print(_("Exporting .."))
+             #response = HttpResponse(content_type='text/csv')
+             #response['Content-Disposition'] = 'attachment; filename=hostedCustomerReport.csv'
+             filehandle = open("/tmp/tmpexport.csv", 'w')
+             writer = csv.DictWriter(filehandle, fieldnames=fieldnames)
+             #writer = csv.writer(response)
+             headers = dict()
+             for field in fieldnames:
+                 headers[field] = field
+             writer.writerow(headers)
+             for pv in ProductVariation.objects.all():
+                 row = dict()
+                 row[TITLE] = pv.product.title
+                 row[CONTENT] = pv.product.content
+                 row[DESCRIPTION] = pv.product.description
+                 row[SKU] = pv.sku
+                 row[IMAGE] = pv.image
+                 # TODO: handle multiple categories, and multiple levels of categories
+                 cat = pv.product.categories.all()[0]
+                 if cat.parent:
+                     row[SUB_CATEGORY] = cat.title
+                     row[CATEGORY] = cat.parent.title
+                 else:
+                     row[CATEGORY] = cat.title
+                     row[SUB_CATEGORY] = ""
+ 
+                 for option in TYPE_CHOICES:
+                     row[option] = getattr(pv, "option%s" % TYPE_CHOICES[option])
+ 
+                 row[NUM_IN_STOCK] = pv.num_in_stock
+                 row[UNIT_PRICE] = pv.unit_price
+                 row[SALE_PRICE] = pv.sale_price
+                 try:
+                     row[SALE_START_DATE] = pv.sale_from.strftime(DATE_FORMAT)
+                     row[SALE_START_TIME] = pv.sale_from.strftime(TIME_FORMAT)
+                 except AttributeError:
+                     pass
+                 try:
+                     row[SALE_END_DATE] = pv.sale_to.strftime(DATE_FORMAT)
+                     row[SALE_END_TIME] = pv.sale_to.strftime(TIME_FORMAT)
+                 except AttributeError:
+                     pass
+                 writer.writerow(row)
+             filehandle.close()
+             f = open('/tmp/tmpexport.csv', 'r')
+             response = HttpResponse(f, content_type='text/csv')
+             response['Content-Disposition'] = 'attachment; filename=product-info.csv'
+             return response
+ 
+         context = {}
+ 
+         if django.VERSION >= (1, 8, 0):
+             context.update(self.admin_site.each_context(request))
+         elif django.VERSION >= (1, 7, 0):
+             context.update(self.admin_site.each_context())
+ 
+         context['form'] = form
+         context['opts'] = self.model._meta
+         request.current_app = self.admin_site.name
+         return TemplateResponse(request, [self.export_template_name],
+                                 context)
+ 
      def save_model(self, request, obj, form, change):
          """
          Store the product object for creating variations in save_formset.

Attachment: admin.py.orig
Description: Binary data

from __future__ import absolute_import, unicode_literals
from future.builtins import filter, int, range, str, super, zip
from future.utils import with_metaclass

from collections import OrderedDict
from copy import copy
from datetime import date
from itertools import dropwhile, takewhile
from locale import localeconv
from re import match

from django import forms
from django.forms.models import BaseInlineFormSet, ModelFormMetaclass
from django.forms.models import inlineformset_factory
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _

from mezzanine.conf import settings
from mezzanine.core.templatetags.mezzanine_tags import thumbnail

from cartridge.shop import checkout
from cartridge.shop.models import Product, ProductOption, ProductVariation
from cartridge.shop.models import Cart, CartItem, Order, DiscountCode
from cartridge.shop.utils import (make_choices, set_locale, set_shipping,
                                  clear_session)


ADD_PRODUCT_ERRORS = {
    "invalid_options": _("The selected options are currently unavailable."),
    "no_stock": _("The selected options are currently not in stock."),
    "no_stock_quantity": _("The selected quantity is currently unavailable."),
}


class AddProductForm(forms.Form):
    """
    A form for adding the given product to the cart or the
    wishlist.
    """

    quantity = forms.IntegerField(label=_("Quantity"), min_value=1)
    sku = forms.CharField(required=False, widget=forms.HiddenInput())

    def __init__(self, *args, **kwargs):
        """
        Handles adding a variation to the cart or wishlist.

        When adding from the product page, the product is provided
        from the view and a set of choice fields for all the
        product options for this product's variations are added to
        the form. When the form is validated, the selected options
        are used to determine the chosen variation.

        A ``to_cart`` boolean keyword arg is also given specifying
        whether the product is being added to a cart or wishlist.
        If a product is being added to the cart, then its stock
        level is also validated.

        When adding to the cart from the wishlist page, a sku is
        given for the variation, so the creation of choice fields
        is skipped.
        """
        self._product = kwargs.pop("product", None)
        self._to_cart = kwargs.pop("to_cart")
        super(AddProductForm, self).__init__(*args, **kwargs)
        # Adding from the wishlist with a sku, bail out.
        if args[0] is not None and args[0].get("sku", None):
            return
        # Adding from the product page, remove the sku field
        # and build the choice fields for the variations.
        del self.fields["sku"]
        option_fields = ProductVariation.option_fields()
        if not option_fields:
            return
        option_names, option_labels = list(zip(*[(f.name, f.verbose_name)
            for f in option_fields]))
        option_values = list(zip(*self._product.variations.filter(
            unit_price__isnull=False).values_list(*option_names)))
        if option_values:
            for i, name in enumerate(option_names):
                values = [_f for _f in set(option_values[i]) if _f]
                if values:
                    field = forms.ChoiceField(label=option_labels[i],
                                              choices=make_choices(values))
                    self.fields[name] = field

    def clean(self):
        """
        Determine the chosen variation, validate it and assign it as
        an attribute to be used in views.
        """
        if not self.is_valid():
            return
        # Posted data will either be a sku, or product options for
        # a variation.
        data = self.cleaned_data.copy()
        quantity = data.pop("quantity")
        # Ensure the product has a price if adding to cart.
        if self._to_cart:
            data["unit_price__isnull"] = False
        error = None
        if self._product is not None:
            # Chosen options will be passed to the product's
            # variations.
            qs = self._product.variations
        else:
            # A product hasn't been given since we have a direct sku.
            qs = ProductVariation.objects
        try:
            variation = qs.get(**data)
        except ProductVariation.DoesNotExist:
            error = "invalid_options"
        else:
            # Validate stock if adding to cart.
            if self._to_cart:
                if not variation.has_stock():
                    error = "no_stock"
                elif not variation.has_stock(quantity):
                    error = "no_stock_quantity"
        if error is not None:
            raise forms.ValidationError(ADD_PRODUCT_ERRORS[error])
        self.variation = variation
        return self.cleaned_data


class CartItemForm(forms.ModelForm):
    """
    Model form for each item in the cart - used for the
    ``CartItemFormSet`` below which controls editing the entire cart.
    """

    class Meta:
        model = CartItem
        fields = ("quantity",)

    def clean_quantity(self):
        """
        Validate that the given quantity is available.
        """
        variation = ProductVariation.objects.get(sku=self.instance.sku)
        quantity = self.cleaned_data["quantity"]
        if not variation.has_stock(quantity - self.instance.quantity):
            error = ADD_PRODUCT_ERRORS["no_stock_quantity"].rstrip(".")
            raise forms.ValidationError("%s: %s" % (error, quantity))
        return quantity

CartItemFormSet = inlineformset_factory(Cart, CartItem, form=CartItemForm,
                                        can_delete=True, extra=0)


class FormsetForm(object):
    """
    Form mixin that provides template methods for iterating through
    sets of fields by prefix, single fields and finally remaning
    fields that haven't been, iterated with each fieldset made up from
    a copy of the original form, giving access to as_* methods.

    The use case for this is ``OrderForm`` below. It contains a
    handful of fields named with the prefixes ``billing_detail_XXX``
    and ``shipping_detail_XXX``. Using ``FormsetForm`` we can then
    group these into fieldsets in our templates::

        <!-- Fields prefixed with "billing_detail_" -->
        <fieldset>{{ form.billing_detail_fields.as_p }}</fieldset>

        <!-- Fields prefixed with "shipping_detail_" -->
        <fieldset>{{ form.shipping_detail_fields.as_p }}</fieldset>

        <!-- All remaining fields -->
        <fieldset>{{ form.other_fields.as_p }}</fieldset>

    Some other helpers exist for use with an individual field name:

    - ``XXX_field`` returns a fieldset containing the field named XXX
    - ``fields_before_XXX`` returns a fieldset with all fields before
      the field named XXX
    - ``fields_after_XXX`` returns a fieldset with all fields after
      the field named XXX
    """

    def _fieldset(self, field_names):
        """
        Return a subset of fields by making a copy of the form
        containing only the given field names.
        """
        fieldset = copy(self)
        if not hasattr(self, "_fields_done"):
            self._fields_done = []
        fieldset.non_field_errors = lambda *args: None
        names = [f for f in field_names if f not in self._fields_done]
        fieldset.fields = OrderedDict([(f, self.fields[f]) for f in names])
        self._fields_done.extend(names)
        return fieldset

    def values(self):
        """
        Return pairs of label and value for each field.
        """
        for field in self.fields:
            label = self.fields[field].label
            if label is None:
                label = field[0].upper() + field[1:].replace("_", " ")
            yield (label, self.initial.get(field, self.data.get(field, "")))

    def __getattr__(self, name):
        """
        Dynamic fieldset caller - matches requested attribute name
        against pattern for creating the list of field names to use
        for the fieldset.
        """
        if name == "errors":
            return None
        filters = (
            ("^other_fields$", lambda:
                self.fields.keys()),
            ("^hidden_fields$", lambda:
                [n for n, f in self.fields.items()
                 if isinstance(f.widget, forms.HiddenInput)]),
            ("^(\w*)_fields$", lambda name:
                [f for f in self.fields.keys() if f.startswith(name)]),
            ("^(\w*)_field$", lambda name:
                [f for f in self.fields.keys() if f == name]),
            ("^fields_before_(\w*)$", lambda name:
                takewhile(lambda f: f != name, self.fields.keys())),
            ("^fields_after_(\w*)$", lambda name:
                dropwhile(lambda f: f != name, self.fields.keys())[1:]),
        )
        for filter_exp, filter_func in filters:
            filter_args = match(filter_exp, name)
            if filter_args is not None:
                return self._fieldset(filter_func(*filter_args.groups()))
        raise AttributeError(name)


class DiscountForm(forms.ModelForm):

    class Meta:
        model = Order
        fields = ("discount_code",)

    def __init__(self, request, data=None, initial=None, **kwargs):
        """
        Store the request so that it can be used to retrieve the cart
        which is required to validate the discount code when entered.
        """
        super(DiscountForm, self).__init__(
                data=data, initial=initial, **kwargs)
        self._request = request

    def clean_discount_code(self):
        """
        Validate the discount code if given, and attach the discount
        instance to the form.
        """
        code = self.cleaned_data.get("discount_code", "")
        cart = self._request.cart
        if code:
            try:
                discount = DiscountCode.objects.get_valid(code=code, cart=cart)
                self._discount = discount
            except DiscountCode.DoesNotExist:
                error = _("The discount code entered is invalid.")
                raise forms.ValidationError(error)
        return code

    def set_discount(self):
        """
        Assigns the session variables for the discount.
        """
        discount = getattr(self, "_discount", None)
        if discount is not None:
            # Clear out any previously defined discount code
            # session vars.
            names = ("free_shipping", "discount_code", "discount_total")
            clear_session(self._request, *names)
            total = self._request.cart.calculate_discount(discount)
            if discount.free_shipping:
                set_shipping(self._request, _("Free shipping"), 0)
            else:
                # A previously entered discount code providing free
                # shipping may have been entered prior to this
                # discount code beign entered, so clear out any
                # previously set shipping vars.
                clear_session(self._request, "shipping_type", "shipping_total")
            self._request.session["free_shipping"] = discount.free_shipping
            self._request.session["discount_code"] = discount.code
            self._request.session["discount_total"] = str(total)


class OrderForm(FormsetForm, DiscountForm):
    """
    Main Form for the checkout process - ModelForm for the Order Model
    with extra fields for credit card. Used across each step of the
    checkout process with fields being hidden where applicable.
    """

    step = forms.IntegerField(widget=forms.HiddenInput())
    same_billing_shipping = forms.BooleanField(required=False, initial=True,
        label=_("My delivery details are the same as my billing details"))
    remember = forms.BooleanField(required=False, initial=True,
        label=_("Remember my address for next time"))
    card_name = forms.CharField(label=_("Cardholder name"))
    card_type = forms.ChoiceField(label=_("Card type"),
        widget=forms.RadioSelect,
        choices=make_choices(settings.SHOP_CARD_TYPES))
    card_number = forms.CharField(label=_("Card number"))
    card_expiry_month = forms.ChoiceField(label=_("Card expiry month"),
        initial="%02d" % date.today().month,
        choices=make_choices(["%02d" % i for i in range(1, 13)]))
    card_expiry_year = forms.ChoiceField(label=_("Card expiry year"))
    card_ccv = forms.CharField(label=_("CCV"), help_text=_("A security code, "
        "usually the last 3 digits found on the back of your card."))

    class Meta:
        model = Order
        fields = ([f.name for f in Order._meta.fields if
                   f.name.startswith("billing_detail") or
                   f.name.startswith("shipping_detail")] +
                   ["additional_instructions", "discount_code"])

    def __init__(
            self, request, step, data=None, initial=None, errors=None,
            **kwargs):
        """
        Setup for each order form step which does a few things:

        - Calls OrderForm.preprocess on posted data
        - Sets up any custom checkout errors
        - Hides the discount code field if applicable
        - Hides sets of fields based on the checkout step
        - Sets year choices for cc expiry field based on current date
        """

        # ``data`` is usually the POST attribute of a Request object,
        # which is an immutable QueryDict. We want to modify it, so we
        # need to make a copy.
        data = copy(data)

        # Force the specified step in the posted data, which is
        # required to allow moving backwards in steps. Also handle any
        # data pre-processing, which subclasses may override.
        if data is not None:
            data["step"] = step
            data = self.preprocess(data)
        if initial is not None:
            initial["step"] = step

        super(OrderForm, self).__init__(
                request, data=data, initial=initial, **kwargs)
        self._checkout_errors = errors

        # Hide discount code field if it shouldn't appear in checkout,
        # or if no discount codes are active.
        settings.use_editable()
        if not (settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT and
                DiscountCode.objects.active().exists()):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        # A ``hidden_filter`` function is defined that's used for
        # filtering out the fields to hide.
        is_first_step = step == checkout.CHECKOUT_STEP_FIRST
        is_last_step = step == checkout.CHECKOUT_STEP_LAST
        is_payment_step = step == checkout.CHECKOUT_STEP_PAYMENT
        hidden_filter = lambda f: False
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if is_first_step:
                # Hide cc fields for billing/shipping if steps are split.
                hidden_filter = lambda f: f.startswith("card_")
            elif is_payment_step:
                # Hide non-cc fields for payment if steps are split.
                hidden_filter = lambda f: not f.startswith("card_")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all cc fields if payment step is not enabled.
            hidden_filter = lambda f: f.startswith("card_")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and is_last_step:
            # Hide all fields for the confirmation step.
            hidden_filter = lambda f: True
        for field in filter(hidden_filter, self.fields):
            self.fields[field].widget = forms.HiddenInput()
            self.fields[field].required = False

        # Set year choices for cc expiry, relative to the current year.
        year = now().year
        choices = make_choices(list(range(year, year + 21)))
        self.fields["card_expiry_year"].choices = choices

    @classmethod
    def preprocess(cls, data):
        """
        A preprocessor for the order form data that can be overridden
        by custom form classes. The default preprocessor here handles
        copying billing fields to shipping fields if "same" checked.
        """
        if data.get("same_billing_shipping", "") == "on":
            for field in data:
                bill_field = field.replace("shipping_detail", "billing_detail")
                if field.startswith("shipping_detail") and bill_field in data:
                    data[field] = data[bill_field]
        return data

    def clean_card_expiry_year(self):
        """
        Ensure the card expiry doesn't occur in the past.
        """
        try:
            month = int(self.cleaned_data["card_expiry_month"])
            year = int(self.cleaned_data["card_expiry_year"])
        except ValueError:
            # Haven't reached payment step yet.
            return
        n = now()
        if year == n.year and month < n.month:
            raise forms.ValidationError(_("A valid expiry date is required."))
        return str(year)

    def clean(self):
        """
        Raise ``ValidationError`` if any errors have been assigned
        externally, via one of the custom checkout step handlers.
        """
        if self._checkout_errors:
            raise forms.ValidationError(self._checkout_errors)
        return super(OrderForm, self).clean()


#######################
#    ADMIN WIDGETS    #
#######################

class ImageWidget(forms.FileInput):
    """
    Render a visible thumbnail for image fields.
    """
    def render(self, name, value, attrs):
        rendered = super(ImageWidget, self).render(name, value, attrs)
        if value:
            orig = u"%s%s" % (settings.MEDIA_URL, value)
            thumb = u"%s%s" % (settings.MEDIA_URL, thumbnail(value, 48, 48))
            rendered = (u"<a target='_blank' href='%s'>"
                        u"<img style='margin-right:6px;' src='%s'>"
                        u"</a>%s" % (orig, thumb, rendered))
        return mark_safe(rendered)


class MoneyWidget(forms.TextInput):
    """
    Render missing decimal places for money fields.
    """
    def render(self, name, value, attrs):
        try:
            value = float(value)
        except (TypeError, ValueError):
            pass
        else:
            set_locale()
            value = ("%%.%sf" % localeconv()["frac_digits"]) % value
            attrs["style"] = "text-align:right;"
        return super(MoneyWidget, self).render(name, value, attrs)


class ProductAdminFormMetaclass(ModelFormMetaclass):
    """
    Metaclass for the Product Admin form that dynamically assigns each
    of the types of product options as sets of checkboxes for selecting
    which options to use when creating new product variations.
    """
    def __new__(cls, name, bases, attrs):
        for option in settings.SHOP_OPTION_TYPE_CHOICES:
            field = forms.MultipleChoiceField(label=option[1],
                required=False, widget=forms.CheckboxSelectMultiple)
            attrs["option%s" % option[0]] = field
        args = (cls, name, bases, attrs)
        return super(ProductAdminFormMetaclass, cls).__new__(*args)


class ProductAdminForm(with_metaclass(ProductAdminFormMetaclass,
                                      forms.ModelForm)):
    """
    Admin form for the Product model.
    """

    class Meta:
        model = Product
        exclude = []

    def __init__(self, *args, **kwargs):
        """
        Set the choices for each of the fields for product options.
        Also remove the current instance from choices for related and
        upsell products (if enabled).
        """
        super(ProductAdminForm, self).__init__(*args, **kwargs)
        for field, options in list(ProductOption.objects.as_fields().items()):
            self.fields[field].choices = make_choices(options)
        instance = kwargs.get("instance")
        if instance:
            queryset = Product.objects.exclude(id=instance.id)
            if settings.SHOP_USE_RELATED_PRODUCTS:
                self.fields["related_products"].queryset = queryset
            if settings.SHOP_USE_UPSELL_PRODUCTS:
                self.fields["upsell_products"].queryset = queryset


class ProductVariationAdminForm(forms.ModelForm):
    """
    Ensure the list of images for the variation are specific to the
    variation's product.
    """
    def __init__(self, *args, **kwargs):
        super(ProductVariationAdminForm, self).__init__(*args, **kwargs)
        if "instance" in kwargs:
            product = kwargs["instance"].product
            qs = self.fields["image"].queryset.filter(product=product)
            self.fields["image"].queryset = qs


class ProductVariationAdminFormset(BaseInlineFormSet):
    """
    Ensure no more than one variation is checked as default.
    """
    def clean(self):
        super(ProductVariationAdminFormset, self).clean()
        if len([f for f in self.forms if hasattr(f, "cleaned_data") and
            f.cleaned_data.get("default", False)]) > 1:
            error = _("Only one variation can be checked as the default.")
            raise forms.ValidationError(error)


class DiscountAdminForm(forms.ModelForm):
    """
    Ensure only one discount field is given a value and if not, assign
    the error to the first discount field so that it displays correctly.
    """
    def clean(self):
        fields = [f for f in self.fields if f.startswith("discount_")]
        reductions = [self.cleaned_data.get(f) for f in fields
                      if self.cleaned_data.get(f)]
        if len(reductions) > 1:
            error = _("Please enter a value for only one type of reduction.")
            self._errors[fields[0]] = self.error_class([error])
        return super(DiscountAdminForm, self).clean()


class ImportForm(forms.Form):
    import_file = forms.FileField(
        label=_('File to import')
        )
    input_format = forms.ChoiceField(
        label=_('Format'),
        choices=(),
        )
    imgs_dirname = forms.CharField(label=_("Imgs Parent Dirname Eg:/tmp/orig"))

    def __init__(self, import_formats, *args, **kwargs):
        super(ImportForm, self).__init__(*args, **kwargs)
        choices = []
        #for i, f in enumerate(import_formats):
        #    choices.append((str(i), f().get_title(),))
        choices.append((str(0), "csv",))
        if len(import_formats) > 1:
            choices.insert(0, ('', '---'))

        self.fields['input_format'].choices = choices


class ExportForm(forms.Form):
    file_format = forms.ChoiceField(
        label=_('Format'),
        choices=(),
        )

    def __init__(self, formats, *args, **kwargs):
        super(ExportForm, self).__init__(*args, **kwargs)
        choices = []
        #for i, f in enumerate(formats):
        #    choices.append((str(i), f().get_title(),))
        choices.append((str(0), "csv",))
        if len(formats) > 1:
            choices.insert(0, ('', '---'))

        self.fields['file_format'].choices = choices

*** forms.py.orig	2016-05-14 13:43:36.353569854 +0530
--- forms.py	2016-05-14 13:46:52.618308720 +0530
***************
*** 542,544 ****
--- 542,585 ----
              error = _("Please enter a value for only one type of reduction.")
              self._errors[fields[0]] = self.error_class([error])
          return super(DiscountAdminForm, self).clean()
+ 
+ 
+ class ImportForm(forms.Form):
+     import_file = forms.FileField(
+         label=_('File to import')
+         )
+     input_format = forms.ChoiceField(
+         label=_('Format'),
+         choices=(),
+         )
+     imgs_dirname = forms.CharField(label=_("Imgs Parent Dirname Eg:/tmp/orig"))
+ 
+     def __init__(self, import_formats, *args, **kwargs):
+         super(ImportForm, self).__init__(*args, **kwargs)
+         choices = []
+         #for i, f in enumerate(import_formats):
+         #    choices.append((str(i), f().get_title(),))
+         choices.append((str(0), "csv",))
+         if len(import_formats) > 1:
+             choices.insert(0, ('', '---'))
+ 
+         self.fields['input_format'].choices = choices
+ 
+ 
+ class ExportForm(forms.Form):
+     file_format = forms.ChoiceField(
+         label=_('Format'),
+         choices=(),
+         )
+ 
+     def __init__(self, formats, *args, **kwargs):
+         super(ExportForm, self).__init__(*args, **kwargs)
+         choices = []
+         #for i, f in enumerate(formats):
+         #    choices.append((str(i), f().get_title(),))
+         choices.append((str(0), "csv",))
+         if len(formats) > 1:
+             choices.insert(0, ('', '---'))
+ 
+         self.fields['file_format'].choices = choices
+ 

Attachment: forms.py.orig
Description: Binary data

{% extends "admin/base_site.html" %} {% load i18n admin_static admin_modify %} {% load admin_urls %} {% block extrastyle %}{{ block.super }}{% endblock %} {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} {% if not is_popup %} {% block breadcrumbs %} {% endblock %} {% endif %} {% extends "admin/change_list.html" %} {# Original template renders object-tools only when has_add_permission is True. #} {# This hack allows sub templates to add to object-tools #} {% block object-tools %}
    {% block object-tools-items %} {% if has_add_permission %} {{ block.super }} {% endif %} {% endblock %}
{% endblock %} {% extends "admin/import_export/change_list.html" %} {% load i18n %} {% block object-tools-items %}
  • {% trans "Import" %}
  • {% trans "Export" %}
  • {{ block.super }} {% endblock %} {% extends "admin/import_export/base.html" %} {% load i18n %} {% load admin_urls %} {% block breadcrumbs_last %} {% trans "Export" %} {% endblock %} {% block content %}

    {% trans "Export" %}

    {% csrf_token %}
    {% for field in form %}
    {{ field.errors }} {{ field.label_tag }} {{ field }} {% if field.field.help_text %}

    {{ field.field.help_text|safe }}

    {% endif %}
    {% endfor %}
    {% endblock %} {% extends "admin/import_export/base.html" %} {% load i18n %} {% load admin_urls %} {% block breadcrumbs_last %} {% trans "Import" %} {% endblock %} {% block content %}

    {% trans "Import" %}

    {% if confirm_form %} {% csrf_token %} {{ confirm_form.as_p }}

    {% trans "Below is a preview of data to be imported. If you are satisfied with the results, click 'Confirm import'" %}

    {% else %} {% csrf_token %}

    {% trans "This importer will import the following fields: " %} {{ fields|join:", " }}

    {% for field in form %}
    {{ field.errors }} {{ field.label_tag }} {{ field }} {% if field.field.help_text %}

    {{ field.field.help_text|safe }}

    {% endif %}
    {% endfor %}
    {% endif %} {% if result %} {% if result.has_errors %}

    {% trans "Errors" %}

      {% for error in result.base_errors %}
    • {{ error.error }}
      {{ error.traceback|linebreaks }}
    • {% endfor %} {% for line, errors in result.row_errors %} {% for error in errors %}
    • {% trans "Line number" %}: {{ line }} - {{ error.error }}
      {{ error.row.values|join:", " }}
      {{ error.traceback|linebreaks }}
    • {% endfor %} {% endfor %}
    {% else %}

    {% trans "Preview" %}

    {% for field in result.diff_headers %} {% endfor %} {% for row in result.rows %} {% for field in row.diff %} {% endfor %} {% endfor %}
    {{ field }}
    {% if row.import_type == 'new' %} {% trans "New" %} {% elif row.import_type == 'skip' %} {% trans "Skipped" %} {% elif row.import_type == 'delete' %} {% trans "Delete" %} {% elif row.import_type == 'update' %} {% trans "Update" %} {% endif %} {{ field }}
    {% endif %} {% endif %} {% endblock %}

    Reply via email to