def test_order_refunds_with_other_lines(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() supplier.shops.add(shop) product = create_product("sku", shop=shop, default_price=10) shop_product = product.get_shop_instance(shop=shop) shop_product.suppliers.set([supplier]) order = create_empty_order(shop=shop) order.full_clean() order.save() add_product_to_order(order, supplier, product, 4, 5) # Lines without quantity shouldn't affect refunds other_line = OrderLine( order=order, type=OrderLineType.OTHER, text="This random line for textual information", quantity=0) other_line.save() order.lines.add(other_line) # Lines with quantity again should be able to be refunded normally. other_line_with_quantity = OrderLine( order=order, type=OrderLineType.OTHER, text="Special service 100$/h", quantity=1, base_unit_price_value=100) other_line_with_quantity.save() order.lines.add(other_line_with_quantity) assert other_line_with_quantity.max_refundable_quantity == 1 assert other_line_with_quantity.max_refundable_amount.value == 100 order.cache_prices() order.create_payment(order.taxful_total_price) assert order.is_paid() assert order.taxful_total_price_value == 120 # 100 + 4 * 20 def get_refund_view_content(): request = apply_request_middleware(rf.get("/"), user=admin_user) view = OrderCreateRefundView.as_view() response = view(request, pk=order.pk) return BeautifulSoup(response.render().content) refund_soup = get_refund_view_content() refund_options = refund_soup.find(id="id_form-0-line_number").findAll("option") assert len(refund_options) == 4 # 1 empty line, 1 for arbitrary and 2 for lines assert len([option for option in refund_options if "Special service 100$/h" in force_text(option)]) == 1
def __init__(self, changing_user, *args, **kwargs): super(PermissionChangeFormBase, self).__init__(*args, **kwargs) self.changing_user = changing_user if not getattr(self.changing_user, 'is_superuser', False): self.fields.pop("is_superuser") if not (self.changing_user == self.instance or getattr(self.instance, 'is_superuser', False)): # Only require old password when editing self.fields.pop("old_password") if "is_superuser" in self.fields: self.fields["is_superuser"].label = _( "Superuser (Full rights) status") self.fields["is_superuser"].help_text = _( "Designates whether this user has all permissions without explicitly " "assigning them. Assigning Granular Permission Groups to a Superuser " "will not have any effect because Granular Permission Groups are only " " able to give more rights, but Superuser already has them all." ) self.fields["is_staff"].label = _("Access to Admin Panel status") self.fields["is_staff"].help_text = _( "Designates whether this user can log into this admin site. Even " "Superusers should have this status enabled, otherwise they won't " "be able to access the Admin Panel.") permission_groups_field = Select2MultipleField( model=PermissionGroup, required=False, label=_("Granular Permission Groups"), help_text= _("Use Permission Groups to granularly give more permissions. User " "can belong to many groups and their permissions add and stack together. " "Search for `Permission Groups` to change these and add them to " "multiple users. Go to user account -> `Actions` -> `Edit Main " "Permissions` to add them to a specific user. Will not influence " "Superusers as they already have all the rights and can't be " "stripped of them without removing Superuser status first.")) initial_groups = self._get_initial_groups() permission_groups_field.initial = [ group.pk for group in initial_groups ] permission_groups_field.widget.choices = [(group.pk, force_text(group)) for group in initial_groups] self.fields["permission_groups"] = permission_groups_field
def get_running_reference_number(order): from shuup import configuration from shuup.admin.modules.settings.consts import ( ORDER_REFERENCE_NUMBER_LENGTH_FIELD, ORDER_REFERENCE_NUMBER_PREFIX_FIELD, ) value = Counter.get_and_increment(CounterType.ORDER_REFERENCE) prefix = "%s" % configuration.get(order.shop, ORDER_REFERENCE_NUMBER_PREFIX_FIELD, settings.SHUUP_REFERENCE_NUMBER_PREFIX) ref_length = configuration.get(order.shop, ORDER_REFERENCE_NUMBER_LENGTH_FIELD, settings.SHUUP_REFERENCE_NUMBER_LENGTH) padded_value = force_text(value).rjust(ref_length - len(prefix), "0") reference_no = "%s%s" % (prefix, padded_value) return reference_no + calc_reference_number_checksum(reference_no)
def matches(self, target): """ Evaluate this Pattern against the target. :type target: str :rtype: bool """ target = force_text(target) if target in self.negative_pieces: return False if any(self._test_piece(piece, target) for piece in self.negative_pieces): return False if "*" in self.positive_pieces or target in self.positive_pieces: return True return any(self._test_piece(piece, target) for piece in self.positive_pieces)
def get_known_additional_data(self): """ Get a list of "known additional data" in this order's `payment_data`, `shipping_data` and `extra_data`. The list is returned in the order the fields are specified in the settings entries for said known keys. `dict(that_list)` can of course be used to "flatten" the list into a dict. :return: list of 2-tuples. """ output = [] for data_dict, name_mapping in ( (self.payment_data, settings.SHUUP_ORDER_KNOWN_PAYMENT_DATA_KEYS), (self.shipping_data, settings.SHUUP_ORDER_KNOWN_SHIPPING_DATA_KEYS), (self.extra_data, settings.SHUUP_ORDER_KNOWN_EXTRA_DATA_KEYS), ): if hasattr(data_dict, "get"): for key, display_name in name_mapping: if key in data_dict: output.append((force_text(display_name), data_dict[key])) return output
def __init__(self, *args, **kwargs): self.request = kwargs.pop("request") self.shop = get_shop(self.request) super(CouponCodeForm, self).__init__(*args, **kwargs) if self.instance.pk: self.fields["coupon_code_discounts"] = Select2MultipleField( label=_("Product Discounts"), help_text=_("Select discounts linked to this coupon code."), model=Discount, required=False) initial_discounts = (self.instance.coupon_code_discounts.all() if self.instance.pk else []) self.fields["coupon_code_discounts"].initial = initial_discounts self.fields["coupon_code_discounts"].widget.choices = [ (discount.pk, force_text(discount)) for discount in initial_discounts ]
def handle_customer_exists(self, request): field = request.GET["field"] value = request.GET["value"] if field in [f.name for f in Contact._meta.get_fields()]: contact_model = Contact elif field in [f.name for f in CompanyContact._meta.get_fields()]: contact_model = CompanyContact elif field in [f.name for f in PersonContact._meta.get_fields()]: contact_model = PersonContact else: return {"error": "Error! Invalid field name."} customer = contact_model.objects.filter(**{field: value}).first() if customer: return {"id": customer.pk, "name": force_text(customer)} else: return {}
def media_upload(request, *args, **kwargs): shop = get_shop(request) try: folder_id = int( request.POST.get("folder_id") or request.GET.get("folder_id") or 0) path = request.POST.get("path") or request.GET.get("path") or None if folder_id != 0: folder = _get_folder_query(shop).get(pk=folder_id) elif path: folder = get_or_create_folder(shop, path) else: folder = None # Root folder upload. How bold! except Exception as exc: return JsonResponse( {"error": "Error! Invalid folder `%s`." % force_text(exc)}, status=400) return _process_form(request, folder)
def address_as_string_list(self, address, locale=None): assert issubclass(type(address), Address) locale = locale or get_current_babel_locale() country = address.country.code.upper() base_lines = [ address.company_name, address.full_name, address.name_ext, address.street, address.street2, address.street3, "%s %s %s" % (address.postal_code, address.city, address.region_code or address.region), locale.territories.get(country, country) if not address.is_home else None ] stripped_lines = [ force_text(line).strip() for line in base_lines if line ] return [s for s in stripped_lines if (s and len(s) > 1)]
def get_object_abstract(self, instance, item): bits = filter(None, [ _("First Name: %s") % (getattr(instance, 'first_name', None) or "\u2014"), _("Last Name: %s") % (getattr(instance, 'last_name', None) or "\u2014"), _("Active") if instance.is_active else _(u"Inactive"), _("Email: %s") % (getattr(instance, 'email', None) or "\u2014"), _("Access to Admin Panel") if getattr(instance, 'is_staff', None) else None, _("Superuser (Full rights)") if getattr(instance, 'is_superuser', None) else None ]) return [{ "text": instance.get_username() or _("User"), "class": "header" }, { "text": ", ".join([force_text(bit) for bit in bits]) }]
def get_context_data(self, **kwargs): context = super(ContactDetailView, self).get_context_data(**kwargs) context["toolbar"] = ContactDetailToolbar(contact=self.object, request=self.request) context["title"] = "%s: %s" % ( self.object._meta.verbose_name.title(), force_text(self.object) ) context["contact_sections"] = [] contact_sections_provides = sorted(get_provide_objects("admin_contact_section"), key=lambda x: x.order) for admin_contact_section in contact_sections_provides: # Check whether the ContactSection should be visible for the current object if admin_contact_section.visible_for_object(self.object, self.request): context["contact_sections"].append(admin_contact_section) # add additional context data where the key is the contact_section identifier section_context = admin_contact_section.get_context_data(self.object, self.request) context[admin_contact_section.identifier] = section_context return context
def get_context_data(self, **kwargs): context = super(OrderDetailView, self).get_context_data(**kwargs) context["toolbar"] = self.get_toolbar() context["title"] = force_text(self.object) context["order_sections"] = [] order_sections_provides = sorted( get_provide_objects("admin_order_section"), key=lambda x: x.order) for admin_order_section in order_sections_provides: # Check whether the Section should be visible for the current object if admin_order_section.visible_for_object(self.object, self.request): context["order_sections"].append(admin_order_section) # Add additional context data where the key is the order_section identifier section_context = admin_order_section.get_context_data( self.object, self.request) context[admin_order_section.identifier] = section_context return context
def get_search_results(self, request, query): shop = request.shop minimum_query_length = 3 skus_seen = set() if len(query) >= minimum_query_length: pk_counter = Counter() pk_counter.update( Product.objects.filter(sku__startswith=query).values_list( "pk", flat=True)) name_q = Q() for part in split_query(query, minimum_query_length): name_q &= Q(name__icontains=part) pk_counter.update( Product._parler_meta.root_model.objects.filter( name_q).values_list("master_id", flat=True)) pks = [pk for (pk, count) in pk_counter.most_common(10)] for product in Product.objects.filter( pk__in=pks, shop_products__shop_id=shop.id): relevance = 100 - pk_counter.get(product.pk, 0) skus_seen.add(product.sku.lower()) yield SearchResult( text=force_text(product), url=get_model_url(product, shop=request.shop), category=_("Products"), relevance=relevance, ) if len(query) >= minimum_query_length: url = reverse("shuup_admin:shop_product.new") if " " in query: yield SearchResult( text=_('Create Product Called "%s"') % query, url=manipulate_query_string(url, name=query), is_action=True, ) else: if query.lower() not in skus_seen: yield SearchResult( text=_('Create Product with SKU "%s"') % query, url=manipulate_query_string(url, sku=query), is_action=True, )
def __init__(self, *args, **kwargs): self.request = kwargs.pop("request") self.shop = get_shop(self.request) super(HappyHourForm, self).__init__(*args, **kwargs) if self.instance.pk: self.fields["discounts"] = ObjectSelect2MultipleField( label=_("Product Discounts"), help_text=_("Select discounts for this happy hour."), model=Discount, required=False, ) initial_discounts = self.instance.discounts.all( ) if self.instance.pk else [] self.fields["discounts"].initial = initial_discounts self.fields["discounts"].widget.choices = [ (discount.pk, force_text(discount)) for discount in initial_discounts ] if self.instance.pk: weekdays, from_hour, to_hour = _get_initial_data_for_time_ranges( self.instance) if weekdays and from_hour and to_hour: self.fields["weekdays"].initial = weekdays self.fields["from_hour"].initial = from_hour self.fields["to_hour"].initial = to_hour # Since we touch these views in init we need to reset some # widgets and help texts after setting the initial values. self.fields["from_hour"].widget = TimeInput(attrs={"class": "time"}) self.fields["to_hour"].widget = TimeInput(attrs={"class": "time"}) help_texts = [ ("from_hour", _("12pm is considered noon and 12am as midnight. Start hour is included to the discount." )), ("to_hour", _("12pm is considered noon and 12am as midnight. End hours is included to the discount." )), ("weekdays", _("Weekdays the happy hour is active.")), ] for field, help_text in help_texts: self.fields[field].help_text = help_text
def __init__(self, attrs=None, choices=(), editable_model=None, initial=None, model=None): """ :param initial list[int]: list of primary keys of the objects that are initially selected """ if model is not None: self.model = model if self.model and initial: choices = [(instance.pk, force_text(instance)) for instance in initial] super(QuickAddRelatedObjectMultipleSelectMixin, self).__init__(attrs, choices, editable_model)
def _handle_related_value(self, field, mapping, orig_value, row_session, obj, value): has_related = False if mapping.get("fk"): value = self._handle_row_fk_value(field, orig_value, row_session, value) if not field.null and value is None: has_related = True elif mapping.get("m2m"): self._handle_row_m2m_value(field, orig_value, row_session, obj, value) has_related = True elif mapping.get("is_enum_field"): for k, v in field.get_choices(): if fold_mapping_name( force_text(v)) == fold_mapping_name(orig_value): value = k break return (value, has_related)
def post(self, request, *args, **kwargs): """ Post action is where Mass Actions post their data. """ data = request.body.decode("utf-8") data = json.loads(data) action_identifier = data.get("action", None) ids = data.get("values", []) mass_action = self._get_mass_action(action_identifier) if mass_action is None: return JsonResponse({ "error": force_text(_("Mass Action encountered an unknown error.")) }) if isinstance(mass_action, PicotableFileMassAction): return mass_action.process(request, ids) mass_action.process(request, ids) return JsonResponse({"ok": True})
def __init__(self, *args, **kwargs): self.request = kwargs.pop("request") super(ManufacturerForm, self).__init__(*args, **kwargs) # add shops field when superuser only if getattr(self.request.user, "is_superuser", False): self.fields["shops"] = Select2MultipleField( label=_("Shops"), help_text= _("Select shops for this manufacturer. Keep it blank to share with all shops." ), model=Shop, required=False, ) initial_shops = self.instance.shops.all( ) if self.instance.pk else [] self.fields["shops"].widget.choices = [(shop.pk, force_text(shop)) for shop in initial_shops] else: # drop shops fields self.fields.pop("shops", None)
def __init__(self, theme, shop, view_name, draft, global_type=False): """ Initialize a view configuration. :param theme: Theme object (could be None to not touch the database). :type theme: shuup.xtheme.Theme|None :param shop: Shop object. :type shop: shuup.core.models.Shop :param view_name: View name (the class name of the view). :type view_name: str :param draft: Load in draft mode? :type draft: bool :param global_type: Boolean indicating whether this is a global config. :type global_type: bool|False """ self.theme = theme self.shop = shop self.view_name = (XTHEME_GLOBAL_VIEW_NAME if global_type else force_text(view_name)) self.draft = bool(draft) self._saved_view_config = None
def __init__(self, *args, **kwargs): self.request = kwargs.pop("request") self.shop = get_shop(self.request) super(AvailabilityExceptionForm, self).__init__(*args, **kwargs) if self.instance.pk: self.fields["discounts"] = Select2MultipleField( label=_("Product Discounts"), help_text=_( "Select discounts to be ignored on given time frame."), model=Discount, required=False, ) initial_discounts = self.instance.discounts.all( ) if self.instance.pk else [] self.fields["discounts"].initial = initial_discounts self.fields["discounts"].widget.choices = [ (discount.pk, force_text(discount)) for discount in initial_discounts ]
def __init__(self, pattern_text): """ Compile a pattern from the given `pattern_text`. Patterns are comma-separated atoms of the forms: * `*` -- matches anything * `text` -- matched directly * `min-max` -- inclusive range matched lexicographically OR as integers if possible * `wild*` -- wildcards (asterisks and question marks allowed) In addition, atoms may be prefixed with `!` to negate them. For instance, "10-20,!15" would match all strings (or numbers) between 10 and 20, but not 15. :type pattern_text: str """ self.pattern_text = pattern_text self.positive_pieces = set() self.negative_pieces = set() for piece in force_text(self.pattern_text).split(","): piece = piece.strip() if not piece: continue if piece.startswith("!"): negate = True piece = piece[1:].strip() else: negate = False if "-" in piece: (min, max) = piece.split("-", 1) piece = (min.strip(), max.strip()) else: piece = (piece.strip(), piece.strip()) if negate: self.negative_pieces.add(piece) else: self.positive_pieces.add(piece)
def __init__(self, *args, **kwargs): self.request = kwargs.pop("request") super(SupplierBaseForm, self).__init__(*args, **kwargs) # add shops field when superuser only if getattr(self.request.user, "is_superuser", False): initial_shops = (self.instance.shops.all() if self.instance.pk else []) self.fields["shops"] = Select2MultipleField( label=_("Shops"), help_text=_("Select shops for this supplier. Keep it blank to share with all shops."), model=Shop, required=False, initial=initial_shops ) self.fields["shops"].choices = initial_shops self.fields["shops"].widget.choices = [ (shop.pk, force_text(shop)) for shop in initial_shops ] else: # drop shops fields self.fields.pop("shops", None) shop = get_shop(self.request) self.fields["is_approved"] = forms.BooleanField( label=_("Is approved for {}").format(shop), required=False, initial=True, help_text=_("Indicates whether this supplier is approved for the shop."), ) if self.instance.pk: supplier_shop = SupplierShop.objects.filter(shop=shop, supplier=self.instance).first() self.fields["is_approved"].initial = bool(supplier_shop and supplier_shop.is_approved) else: self.fields["is_approved"].initial = False choices = Supplier.get_module_choices( empty_label=(_("No %s module") % Supplier._meta.verbose_name) ) self.fields["module_identifier"].choices = self.fields["module_identifier"].widget.choices = choices
def __init__(self, url, **kwargs): """ :param url: The URL to navigate to. For convenience, if this contains no slashes, `reverse` is automatically called on it. :type url: str """ if "/" not in url: try: url = reverse(url) except NoReverseMatch: pass self.url = url if "required_permissions" not in kwargs: try: permission = resolve(six.moves.urllib.parse.urlparse(force_text(url)).path).url_name kwargs["required_permissions"] = (permission,) except Resolver404: pass super(URLActionButton, self).__init__(**kwargs)
def test_deleting_script(rf, admin_user): shop = factories.get_default_shop() request = apply_request_middleware(rf.get("/"), user=admin_user) script = Script.objects.create(shop=shop, event_identifier="order_received", name="Script 1", enabled=True) delete_url = reverse("shuup_admin:notify.script.delete", kwargs={"pk": script.pk}) view = ScriptEditView.as_view() response = view(request, pk=script.pk).render() assert bool(delete_url in force_text(response.content)) assert Script.objects.count() == 1 request = apply_request_middleware(rf.post("/"), user=admin_user) delete_view = ScriptDeleteView.as_view() response = delete_view(request, pk=script.pk) assert response.status_code == 302 # Redirect to list view assert Script.objects.count() == 0
def add_code(self, code): """ Add a code to this OrderSource. At this point it is expected that the customers permission to use the code has already been checked by the caller. The code will be converted to text. :param code: The code to add. :type code: str :return: True if code was added, False if it was already there. :rtype: bool """ code_text = force_text(code) if code_text.upper() not in [c.upper() for c in self._codes]: self._codes.append(code_text) self.uncache() return True return False
def __init__(self, **kwargs): super(ShopBaseForm, self).__init__(**kwargs) self.fields["currency"] = forms.ChoiceField( choices=get_currency_choices(), required=True, label=_("Currency"), help_text=_("The primary shop currency. This is the currency used when selling your products.") ) staff_members = Select2MultipleField( label=_("Staff"), help_text=_("Select staff members for this shop."), model=get_user_model(), required=False ) staff_members.widget = QuickAddUserMultiSelect(attrs={"data-model": "auth.User"}) initial_members = (self.instance.staff_members.all() if self.instance.pk else []) staff_members.widget.choices = [(member.pk, force_text(member)) for member in initial_members] self.fields["staff_members"] = staff_members self.fields["domain"].required = ShuupSettings.get_setting("SHUUP_ENABLE_MULTIPLE_SHOPS") self.disable_protected_fields()
def generate_multilanguage_slugs(object, name_getter, slug_length=128): translations_model = object._parler_meta.root_model for language_code, language_name in settings.LANGUAGES: try: translation = object.get_translation(language_code=language_code) translation.refresh_from_db() except ObjectDoesNotExist: # For some reason the `get_translation` raises if the object is created recently translation = translations_model.objects.filter( master_id=object.id, language_code=language_code).first() if not translation: # Guessing the translation is deleted recently so let's just skip this language continue # Since slugs can be set by the merchant let's not override if already set if not translation or translation.slug: continue name = force_text(name_getter(object, translation)) slug = slugify(name) translation.slug = (slug[:slug_length] if slug else None) translation.save()
def _render_cell(self, write, layout, x, cell): """ Render a layout cell into HTML. :param write: Writer function :type write: callable :param x: Cell X coordinate :type x: int :param cell: Cell :type cell: shuup.xtheme.view_config.LayoutCell """ classes = ["xt-ph-cell"] for breakpoint, width in cell.sizes.items(): if width is None or width == 0: continue if width < 0: classes.append(layout.hide_cell_class_template % { "breakpoint": breakpoint, "width": width }) else: classes.append(layout.cell_class_template % { "breakpoint": breakpoint, "width": width }) classes.append(cell.align) if cell.extra_classes: classes.append(cell.extra_classes) cell_attrs = {"class": classes} if self.edit: cell_attrs.update({"data-xt-cell": str(x)}) write("<div%s>" % get_html_attrs(cell_attrs)) content = cell.render(self.context) if content is not None: # pragma: no branch write(force_text(content)) write("</div>")
def __init__(self, model, required=True, label=None, initial=None, help_text='', extra_widget_attrs={}, *args, **kwargs): widget_attrs = {"data-model": model} widget_attrs.update(extra_widget_attrs) choices = [] if initial: from django.apps import apps app_label, model_name = model.split(".") model = apps.get_model(app_label, model_name) instance = model.objects.filter(pk=initial).first() if instance: choices = [(instance.pk, force_text(instance))] super(XThemeSelect2ModelChoiceField, self).__init__( choices=choices, required=required, widget=forms.Select(attrs=widget_attrs), label=label, initial=initial, help_text=help_text, *args, **kwargs )
def get_display_value(self, context, object): # Look for callable from view context display_callable = maybe_callable(self.display, context=context) if display_callable: return display_callable(object) # Look for callable from provided column objects contexts display_callable = self.search_from_provided_contexts(object) if display_callable: return display_callable(object) value = object for bit in self.display.split("__"): value = getattr(value, bit, None) return_value = self.check_different_types(value) if return_value is not None: return return_value if not value: value = "" return force_text(value)