class AvailabilityExceptionListView(PicotableListView): model = AvailabilityException url_identifier = "discounts_availability_exception" default_columns = [ Column("name", _("Exception Name"), sort_field="name", display="name", filter_config=TextFilter(filter_field="name", placeholder=_("Filter by name..."))), Column("start_datetime", _("Start Date and Time"), display="format_start_datetime", filter_config=DateRangeFilter()), Column("end_datetime", _("End Date and Time"), display="format_end_datetime", filter_config=DateRangeFilter()) ] def get_queryset(self): return AvailabilityException.objects.filter( shops=get_shop(self.request)) def format_start_datetime(self, instance, *args, **kwargs): return get_locally_formatted_datetime( instance.start_datetime) if instance.start_datetime else "" def format_end_datetime(self, instance, *args, **kwargs): return get_locally_formatted_datetime( instance.end_datetime) if instance.end_datetime else ""
def get_pico(rf, admin_user, model=None, columns=None): shop = get_default_shop() model = model or get_user_model() columns = columns or [ Column("id", "Id", filter_config=Filter(), display=instance_id), Column("username", "Username", sortable=False, filter_config=MultiFieldTextFilter( filter_fields=("username", "email"), operator="iregex")), Column("email", "Email", sortable=False, filter_config=TextFilter()), Column("is_superuser", "Is Superuser", display="superuser_display", filter_config=ChoicesFilter(choices=false_and_true())), Column("is_active", "Is Active", filter_config=ChoicesFilter( choices=false_and_true)), # `choices` callable Column("date_joined", "Date Joined", filter_config=DateRangeFilter()) ] admin_user.is_staff = True admin_user.save() shop.staff_members.add(admin_user) request = apply_request_middleware(rf.get("/"), user=admin_user) return Picotable(request=request, columns=columns, mass_actions=[], queryset=model.objects.all(), context=PicoContext(request))
def get_pico(rf, model=None, columns=None): model = model or get_user_model() columns = columns or [ Column("id", "Id", filter_config=Filter(), display=instance_id), Column("username", "Username", sortable=False, filter_config=MultiFieldTextFilter( filter_fields=("username", "email"), operator="iregex")), Column("email", "Email", sortable=False, filter_config=TextFilter()), Column("is_superuser", "Is Superuser", display="superuser_display", filter_config=ChoicesFilter(choices=false_and_true())), Column("is_active", "Is Active", filter_config=ChoicesFilter( choices=false_and_true)), # `choices` callable Column("date_joined", "Date Joined", filter_config=DateRangeFilter()) ] return Picotable(request=rf.get("/"), columns=columns, queryset=model.objects.all(), context=PicoContext())
def test_choice_filter_with_default(rf, admin_user, regular_user): columns = [ Column("id", "Id", filter_config=Filter(), display=instance_id), Column("username", "Username", sortable=False, filter_config=MultiFieldTextFilter(filter_fields=("username", "email"), operator="iregex")), Column("email", "Email", sortable=False, filter_config=TextFilter()), Column("is_superuser", "Is Superuser", display="superuser_display", filter_config=ChoicesFilter(choices=false_and_true())), Column("date_joined", "Date Joined", filter_config=DateRangeFilter()) ] is_active = [ Column("is_active", "Is Active", filter_config=ChoicesFilter(choices=false_and_true)) ] is_active_with_default = [ Column("is_active", "Is Active", filter_config=ChoicesFilter(choices=false_and_true, default=True)) ] query = {"perPage": 100, "page": 1, "sort": "+id"} pico_no_defaults = get_pico(rf, columns=(columns + is_active)) data = pico_no_defaults.get_data(query) user_data = data["items"][0] user = get_user_model().objects.get(id=user_data["id"]) assert user.is_active pico_with_defaults = get_pico(rf, columns=(columns + is_active_with_default)) data = pico_with_defaults.get_data(query) user_data = data["items"][0] user_with_defaults = get_user_model().objects.get(id=user_data["id"]) assert user_with_defaults == user user.is_active = False user.save() data = pico_no_defaults.get_data(query) user_data = data["items"][0] new_user = get_user_model().objects.get(id=user_data["id"]) assert new_user == user data = pico_with_defaults.get_data(query) user_data = data["items"][0] new_user_with_defaults = get_user_model().objects.get(id=user_data["id"]) assert new_user_with_defaults != user_with_defaults
class OrderListView(PicotableListView): model = Order columns = [ Column("identifier", _(u"Order"), linked=True, filter_config=TextFilter(operator="startswith")), Column("order_date", _(u"Order Date"), display="format_order_date", filter_config=DateRangeFilter()), Column("customer", _(u"Customer"), filter_config=MultiFieldTextFilter( filter_fields=("customer__email", "customer__name"))), Column("status", _(u"Status"), filter_config=ChoicesFilter(choices=OrderStatus.objects.all())), Column("payment_status", _(u"Payment Status"), filter_config=ChoicesFilter(choices=PaymentStatus.choices)), Column("shipping_status", _(u"Shipping Status"), filter_config=ChoicesFilter(choices=ShippingStatus.choices)), Column("taxful_total_price", _(u"Total"), sort_field="taxful_total_price_value", display="format_taxful_total_price", class_name="text-right", filter_config=RangeFilter( field_type="number", filter_field="taxful_total_price_value")), ] def get_queryset(self): return super(OrderListView, self).get_queryset().exclude(deleted=True) def format_order_date(self, instance, *args, **kwargs): return get_locally_formatted_datetime(instance.order_date) def format_taxful_total_price(self, instance, *args, **kwargs): return escape(format_money(instance.taxful_total_price)) def get_object_abstract(self, instance, item): return [{ "text": "%s" % instance, "class": "header" }, { "title": _(u"Total"), "text": item["taxful_total_price"] }, { "title": _(u"Status"), "text": item["status"] }]
class CartListView(PicotableListView): model = StoredBasket default_columns = [ Column("key", _(u"Key"), filter_config=TextFilter(filter_field="key")), Column("updated_on", _(u"Last updated on"), display="format_updated_date", filter_config=DateRangeFilter()), Column("finished", _("Completed"), display="format_finished_status", filter_config=ChoicesFilter([(True, _("yes")), (False, _("no"))], filter_field="finished", default=False)), Column("shop", _("Shop"), filter_config=TextFilter( filter_field="shop__translations__public_name")), Column("supplier", _("Supplier"), filter_config=TextFilter(filter_field="supplier__name")), Column("customer", _(u"Customer"), filter_config=MultiFieldTextFilter( filter_fields=("customer__email", "customer__name"))), Column("product_count", _("Product count"), filter_config=RangeFilter()), Column("taxful_total_price", _(u"Total"), sort_field="taxful_total_price_value", display="format_taxful_total_price", class_name="text-right", filter_config=RangeFilter( field_type="number", filter_field="taxful_total_price_value")), ] toolbar_buttons_provider_key = "cart_list_toolbar_provider" mass_actions_provider_key = "cart_list_actions_provider" def __init__(self): super(CartListView, self).__init__() self.columns = self.default_columns def get_queryset(self): """ Ignore potentially active carts, displaying only those not updated for at least 2 hours. """ shop = get_shop(self.request) filters = {"product_count__gte": 0, "persistent": False, "shop": shop} return super(CartListView, self).get_queryset().filter(**filters) def format_finished_status(self, instance, *args, **kwargs): return "yes" if instance.finished else "no" def format_updated_date(self, instance, *args, **kwargs): return get_locally_formatted_datetime(instance.updated_on) def format_taxful_total_price(self, instance, *args, **kwargs): if not instance.taxful_total_price: return "" return escape(format_money(instance.taxful_total_price)) def get_context_data(self, **kwargs): context = super(CartListView, self).get_context_data(**kwargs) context["title"] = _("Carts") return context def get_object_abstract(self, instance, item): return [ { "text": "%s" % instance, "class": "header" }, { "title": _(u"Created on"), "text": item.get("created_on") }, { "title": _(u"Last updated on"), "text": item.get("updated_on") }, { "title": _(u"Ordered"), "text": item.get("finished") }, { "title": _(u"Total"), "text": item.get("taxful_total_price") }, ]
class OrderListView(PicotableListView): model = Order default_columns = [ Column("identifier", _(u"Order"), linked=True, filter_config=TextFilter(operator="startswith")), Column("order_date", _(u"Order Date"), display="format_order_date", filter_config=DateRangeFilter()), Column("customer", _(u"Customer"), display="format_customer_name", filter_config=MultiFieldTextFilter( filter_fields=("customer__email", "customer__name", "billing_address__name", "shipping_address__name", "orderer__name"))), Column( "status", _(u"Status"), filter_config=ChoicesFilter(choices=OrderStatus.objects.all()), ), Column("payment_status", _(u"Payment Status"), filter_config=ChoicesFilter(choices=PaymentStatus.choices)), Column("shipping_status", _(u"Shipping Status"), filter_config=ChoicesFilter(choices=ShippingStatus.choices)), Column("taxful_total_price_value", _(u"Total"), sort_field="taxful_total_price_value", display="format_taxful_total_price", class_name="text-right", filter_config=RangeFilter( field_type="number", filter_field="taxful_total_price_value")), ] related_objects = [ ("shop", "shuup.core.models:Shop"), ("billing_address", "shuup.core.models:ImmutableAddress"), ("shipping_address", "shuup.core.models:ImmutableAddress"), ] mass_actions = [ "shuup.admin.modules.orders.mass_actions:CancelOrderAction", "shuup.admin.modules.orders.mass_actions:OrderConfirmationPdfAction", "shuup.admin.modules.orders.mass_actions:OrderDeliveryPdfAction", ] toolbar_buttons_provider_key = "order_list_toolbar_provider" mass_actions_provider_key = "order_list_mass_actions_provider" def get_toolbar(self): toolbar = Toolbar([ NewActionButton.for_model(Order, url=reverse("shuup_admin:order.new")), SettingsActionButton.for_model(Order, return_url="order") ], view=self) return toolbar def get_queryset(self): return super(OrderListView, self).get_queryset().exclude( deleted=True).filter(shop=get_shop(self.request)) def format_customer_name(self, instance, *args, **kwargs): return instance.get_customer_name() or "" def format_order_date(self, instance, *args, **kwargs): return get_locally_formatted_datetime(instance.order_date) def format_taxful_total_price(self, instance, *args, **kwargs): return escape(format_money(instance.taxful_total_price)) def label(self, instance, *args, **kwargs): # format label to make it human readable return instance.label.replace("_", " ").title() def get_object_abstract(self, instance, item): return [{ "text": "%s" % instance, "class": "header" }, { "title": _(u"Total"), "text": item.get("taxful_total_price_value") }, { "title": _(u"Status"), "text": item.get("status") }]
class DiscountListView(PicotableListView): model = Discount url_identifier = "discounts" default_columns = [ Column("name", _("Discount Name"), sort_field="name", display="name", filter_config=TextFilter(filter_field="name", placeholder=_("Filter by name..."))), Column("product__translations__name", _("Product"), display="product", filter_config=TextFilter( filter_field="product__translations__name", placeholder=_("Filter by product..."))), Column("category", _("Category"), display="category", filter_config=TextFilter( filter_field="category__translations__name", placeholder=_("Filter by category..."))), Column("contact_group", _("Contact Group"), display="contact_group", filter_config=TextFilter( filter_field="contact_group__translations__name", placeholder=_("Filter by contact group..."))), Column("contact", _("Contact"), display="contact", filter_config=TextFilter( filter_field="contact__translations__name", placeholder=_("Filter by contact..."))), Column("coupon_code", _("Coupon code"), display="coupon_code", filter_config=TextFilter( filter_field="coupon_code__code", placeholder=_("Filter by coupon code..."))), Column("discount_effect", _("Effect"), display="get_discount_effect"), Column("end_datetime", _("End Date and Time"), display="format_end_datetime", filter_config=DateRangeFilter()) ] mass_actions = ["shuup.discounts.admin.mass_actions:ArchiveMassAction"] toolbar_buttons_provider_key = "discount_list_toolbar_provider" mass_actions_provider_key = "discount_list_actions_provider" def get_discount_effect(self, instance): if not (instance.discount_amount_value or instance.discounted_price_value or instance.discount_percentage): return "-" effects = [] shop = get_shop(self.request) if instance.discount_amount_value: effects.append( "- %s" % format_money(shop.create_price(instance.discount_amount_value)) if shop else format_number(instance.discount_amount_value)) if instance.discounted_price_value: effects.append( format_money(shop.create_price(instance.discounted_price_value) ) if shop else format_number(instance.discounted_price_value)) if instance.discount_percentage: effects.append(format_percent(instance.discount_percentage)) return ','.join(effects) def format_end_datetime(self, instance, *args, **kwargs): return get_locally_formatted_datetime( instance.end_datetime) if instance.end_datetime else "" def get_queryset(self): return Discount.objects.active(get_shop(self.request))
class CartListView(PicotableListView): model = StoredBasket columns = [ Column("created_on", _(u"Created on"), display="format_created_date", filter_config=DateRangeFilter()), Column("updated_on", _(u"Last updated on"), display="format_updated_date", filter_config=DateRangeFilter()), Column("finished", _("Abandoned"), display="format_abandoned_status", filter_config=ChoicesFilter([(False, _("yes")), (True, _("no"))])), Column("shop", _("Shop"), filter_config=ChoicesFilter(choices=Shop.objects.all())), Column("product_count", _("Product count"), filter_config=RangeFilter()), Column("customer", _(u"Customer"), filter_config=MultiFieldTextFilter( filter_fields=("customer__email", "customer__name"))), Column("orderer", _(u"Orderer"), filter_config=MultiFieldTextFilter( filter_fields=("orderer__email", "orderer__name"))), Column("taxful_total_price", _(u"Total"), sort_field="taxful_total_price_value", display="format_taxful_total_price", class_name="text-right", filter_config=RangeFilter( field_type="number", filter_field="taxful_total_price_value")), ] def get_queryset(self): """ Ignore potentially active carts, displaying only those not updated for at least 2 hours. """ cutoff = now() - datetime.timedelta(hours=2) filters = { "updated_on__lt": cutoff, "product_count__gte": 0, "persistent": False } return super(CartListView, self).get_queryset().filter(**filters) def format_abandoned_status(self, instance, *args, **kwargs): return "yes" if not instance.finished else "no" def format_created_date(self, instance, *args, **kwargs): return get_locally_formatted_datetime(instance.created_on) def format_updated_date(self, instance, *args, **kwargs): return get_locally_formatted_datetime(instance.updated_on) def format_taxful_total_price(self, instance, *args, **kwargs): if not instance.taxful_total_price: return "" return escape(format_money(instance.taxful_total_price)) def get_context_data(self, **kwargs): context = super(CartListView, self).get_context_data(**kwargs) context["title"] = _("Carts") return context def get_object_abstract(self, instance, item): return [ { "text": "%s" % instance, "class": "header" }, { "title": _(u"Created on"), "text": item["created_on"] }, { "title": _(u"Last updated on"), "text": item["updated_on"] }, { "title": _(u"Ordered"), "text": item["finished"] }, { "title": _(u"Total"), "text": item["taxful_total_price"] }, ]
class BaseLogListView(PicotableListView): model = None user_search_field = "user__username" target_search_field = None # Define in parent class default_columns = [ Column("created_on", _("Created on"), ordering=1, sortable=True, filter_config=DateRangeFilter()), Column("user", _("User"), ordering=2, sortable=True, display="format_user", raw=True, filter_config=TextFilter(filter_field=user_search_field, placeholder=_("Filter by user..."))), Column("target", _("Target"), ordering=3, sortable=True, display="format_target", raw=True, filter_config=TextFilter(filter_field=target_search_field, placeholder=_("Filter by target..."))), Column("message", _("Message"), ordering=4, sortable=True, filter_config=TextFilter( filter_field="message", placeholder=_("Filter by message..."))), Column("kind", _("Kind"), ordering=5, sortable=True, filter_config=ChoicesFilter(choices=LogEntryKind.choices)), Column("identifier", _("Identifier"), ordering=6, sortable=True, filter_config=TextFilter( filter_field="identifier", placeholder=_("Filter by identifier..."))), Column("extra", _("Extra"), ordering=7, sortable=True), Column("extra_changed", _("Change in extra"), ordering=8, sortable=True, display="get_extra_change") ] def __init__(self): super(BaseLogListView, self).__init__() self.columns = self.default_columns def format_user(self, instance, *args, **kwargs): if instance.user: return '<a href=%s target="_blank">%s</a>' % (get_model_url( instance.user), instance.user) return "-" def format_target(self, instance, *args, **kwargs): if instance.target: try: return '<a href=%s target="_blank">%s</a>' % (get_model_url( instance.target), instance.target) except Exception as e: return instance.target return "-" def get_extra_change(self, instance, *args, **kwargs): previous_item = self.model.objects.filter( pk__lt=instance.pk, identifier=instance.identifier).order_by("id").first() if previous_item: diff = set(instance.extra.items()) - set( previous_item.extra.items()) return str({k: value for k, value in diff}) return "-" def get_queryset(self): return self.model.objects.all().order_by("-created_on")
class ImportListView(PicotableListView): picotable_class = ImporterPicotable model = BackgroundTaskExecution default_columns = [ Column("started_on", _("Import date"), sortable=True, filter_config=DateRangeFilter()), Column("importer", _("Importer"), sortable=False, display="get_importer"), Column("import_mode", _("Import mode"), sortable=False, display="get_import_mode"), Column("user", _("User"), sort_field="task__user", display="get_user"), Column( "status", _("Status"), sort_field="status", filter_config=ChoicesFilter( BackgroundTaskExecutionStatus.choices()), ), ] toolbar_buttons_provider_key = "import_list_toolbar_provider" mass_actions_provider_key = "import_list_mass_actions_provider" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.columns = self.default_columns def get_importer(self, instance): importer = instance.task.arguments["importer"] return IMPORTER_NAMES_MAP.get(importer, importer) def get_user(self, instance): return str(instance.task.user or "-") def get_import_mode(self, instance): return ImportMode(instance.task.arguments["import_mode"]).label def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["title"] = _("Data imports") return context def get_toolbar(self): toolbar = super().get_toolbar() toolbar.append( NewActionButton(url=reverse("shuup_admin:importer.import.new"), text=_("Import file"), icon="fa fa-upload")) return toolbar def get_queryset(self): return get_imports_queryset(self.request).defer("result", "error_log") def get_object_url(self, instance): return reverse("shuup_admin:importer.import.detail", kwargs=dict(pk=instance.pk)) def get_object_abstract(self, instance, item): return [ { "text": item.get("importer"), "class": "header" }, { "title": _("Importdate"), "text": item.get("started_on") }, { "title": _("Mode"), "text": item.get("import_mode") }, { "title": _("User"), "text": item.get("user") }, { "title": _("Status"), "text": item.get("status") }, ]
class BaseLogListView(PicotableListView): model = None user_search_field = "user__username" target_search_field = None # Define in parent class hide_extra = False hide_extra_changed = False default_columns = [ Column("created_on", _("Created on"), ordering=1, sortable=True, filter_config=DateRangeFilter()), Column("user", _("User"), ordering=2, sortable=True, display="format_user", raw=True, filter_config=TextFilter(filter_field=user_search_field, placeholder=_("Filter by user..."))), Column("target", _("Target"), ordering=3, sortable=True, display="format_target", raw=True, filter_config=TextFilter(filter_field=target_search_field, placeholder=_("Filter by target..."))), Column("message", _("Message"), ordering=4, sortable=True, filter_config=TextFilter( filter_field="message", placeholder=_("Filter by message..."))), Column("kind", _("Kind"), ordering=5, sortable=True, filter_config=ChoicesFilter(choices=LogEntryKind.choices)), Column("identifier", _("Identifier"), ordering=6, sortable=True, filter_config=TextFilter( filter_field="identifier", placeholder=_("Filter by identifier..."))), Column("extra", _("Extra"), ordering=7, sortable=True), Column("extra_changed", _("Change in extra"), ordering=8, sortable=True, display="get_extra_change", raw=True) ] def __init__(self): super(BaseLogListView, self).__init__() self.columns = self.default_columns if self.hide_extra: self.columns = [ column for column in self.default_columns if column.id != "extra" ] if self.hide_extra_changed: self.columns = [ column for column in self.default_columns if column.id != "extra_changed" ] def format_user(self, instance, *args, **kwargs): if instance.user: return '<a href=%s target="_blank">%s</a>' % (get_model_url( instance.user), instance.user) return "-" def format_target(self, instance, *args, **kwargs): if instance.target: try: return '<a href=%s target="_blank">%s</a>' % (get_model_url( instance.target), instance.target) except Exception as e: return instance.target.pk return "-" def get_extra_change(self, instance, *args, **kwargs): if not instance.extra: return "-" previous_item = self.model.objects.filter( pk__lt=instance.pk, target_id=instance.target_id, identifier=instance.identifier).order_by("-id").first() if previous_item and previous_item.extra: changes = [] for action, attr, value_change in diff(previous_item.extra, instance.extra): if isinstance(attr, list): attr = ".".join(str(v) for v in attr) if action == "remove": changes.append( '{} <code>{}</code> <em style="color: red;">{}</em>'. format( action.capitalize(), attr, value_change, )) elif action == "add": changes.append( '{} <code>{}</code> <em style="color: green;">{}</em>'. format( action.capitalize(), attr, value_change, )) elif action == "change": row_fmt = ( '{} <code>{}</code> from <em style="color: red;">{}</em> to <em style="color: green;">{}</em>' ) changes.append( row_fmt.format( action.capitalize(), attr, value_change[0] if value_change[0] is not None else "-", value_change[1] if value_change[1] is not None else "-")) return "<br>".join(changes) return "-" def get_queryset(self): return self.model.objects.all().order_by("-created_on")
class OrderListView(PicotableListView): model = Order default_columns = [ Column("identifier", _(u"Order"), linked=True, filter_config=TextFilter(operator="startswith")), Column("order_date", _(u"Order Date"), display="format_order_date", filter_config=DateRangeFilter()), Column("customer", _(u"Customer"), display="format_customer_name", filter_config=MultiFieldTextFilter( filter_fields=("customer__email", "customer__name", "billing_address__name", "shipping_address__name", "orderer__name"))), Column( "status", _(u"Status"), filter_config=OrderStatusChoicesFilter( choices=OrderStatus.objects.all()), ), Column("payment_status", _(u"Payment Status"), filter_config=ChoicesFilter(choices=PaymentStatus.choices)), Column("shipping_status", _(u"Shipping Status"), filter_config=ChoicesFilter(choices=ShippingStatus.choices)), Column("taxful_total_price_value", _(u"Total"), sort_field="taxful_total_price_value", display="format_taxful_total_price", class_name="text-right", filter_config=RangeFilter( field_type="number", filter_field="taxful_total_price_value")), ] mass_actions = [ "shuup.admin.modules.orders.mass_actions:CancelOrderAction", "shuup.admin.modules.orders.mass_actions:OrderConfirmationPdfAction", "shuup.admin.modules.orders.mass_actions:OrderDeliveryPdfAction", ] def get_queryset(self): return super(OrderListView, self).get_queryset().exclude( deleted=True).filter(shop=self.request.shop) def format_customer_name(self, instance, *args, **kwargs): return instance.get_customer_name() or "" def format_order_date(self, instance, *args, **kwargs): return get_locally_formatted_datetime(instance.order_date) def format_taxful_total_price(self, instance, *args, **kwargs): return escape(format_money(instance.taxful_total_price)) def get_object_abstract(self, instance, item): return [{ "text": "%s" % instance, "class": "header" }, { "title": _(u"Total"), "text": item.get("taxful_total_price_value") }, { "title": _(u"Status"), "text": item.get("status") }]