def mutate(cls, root, info, id, input): restock = input.get('restock') fulfillment = get_node(info, id, only_type=Fulfillment) order = fulfillment.order errors = [] if not fulfillment.can_edit(): errors.append( Error( field='fulfillment', message=pgettext_lazy( 'Cancel fulfillment mutation error', 'This fulfillment can\'t be canceled'))) if errors: return cls(errors=errors) cancel_fulfillment(fulfillment, restock) if restock: msg = npgettext_lazy( 'Dashboard message', 'Restocked %(quantity)d item', 'Restocked %(quantity)d items', 'quantity') % {'quantity': fulfillment.get_total_quantity()} else: msg = pgettext_lazy( 'Dashboard message', 'Fulfillment #%(fulfillment)s canceled') % { 'fulfillment': fulfillment.composed_id} order.history.create(content=msg, user=info.context.user) return FulfillmentCancel(fulfillment=fulfillment)
def get_visibility_text(self): visibility = self.groupcomment_edit_history.visibility if visibility == group_models.GroupComment.VISIBILITY_PRIVATE: return pgettext_lazy('comment edit history item', 'Only visible to you') if visibility == group_models.GroupComment.VISIBILITY_VISIBLE_TO_EXAMINER_AND_ADMINS: return pgettext_lazy('comment edit history item', 'Only visible to examiners and admins') return pgettext_lazy('comment edit history item', 'Visible to everyone')
def orderline_split(request, order_pk, line_pk): order = get_object_or_404(Order, pk=order_pk) item = get_object_or_404(OrderedItem.objects.filter( delivery_group__order=order), pk=line_pk) form = MoveItemsForm(request.POST or None, item=item) line_pk = None if item: line_pk = item.pk status = 200 if form.is_valid(): old_group = item.delivery_group how_many = form.cleaned_data['quantity'] with transaction.atomic(): target_group = form.move_items() if not old_group.pk: old_group = pgettext_lazy( 'Dashboard message related to a delivery group', 'removed group') msg = pgettext_lazy( 'Dashboard message related to delivery groups', 'Moved %(how_many)s items %(item)s from %(old_group)s' ' to %(new_group)s') % { 'how_many': how_many, 'item': item, 'old_group': old_group, 'new_group': target_group} order.create_history_entry(comment=msg, user=request.user) messages.success(request, msg) elif form.errors: status = 400 ctx = {'order': order, 'object': item, 'form': form, 'line_pk': line_pk} template = 'dashboard/order/modal_split_order_line.html' return TemplateResponse(request, template, ctx, status=status)
def clean(self): data = super().clean() if not self.instance.is_staff: return data if self.instance == self.user: raise forms.ValidationError( pgettext_lazy( "Edit customer details in order form error", "You can't delete your own account via dashboard, " "please try from the storefront.", ) ) if self.instance.is_superuser: raise forms.ValidationError( pgettext_lazy( "Edit customer details in order form error", "Only superuser can delete his own account.", ) ) can_manage_staff_users = self.user.has_perm("account.manage_staff") if not can_manage_staff_users: raise forms.ValidationError( pgettext_lazy( "Edit customer details in order form error", "You have insufficient permissions, to edit staff users.", ) ) return data
def get_choices(self): return [ ('', ''), (pgettext_lazy('exact candidate num', 'Exactly'), self.get_exact_choices()), (pgettext_lazy('less than candidate num', 'Less than'), self.get_less_than_choices()), (pgettext_lazy('greater than candidate num', 'Greater than'), self.get_greater_than_choices()) ]
def get_display(status): if status == VariantAvailabilityStatus.AVAILABLE: return pgettext_lazy('Variant status', 'available') elif status == VariantAvailabilityStatus.OUT_OF_STOCK: return pgettext_lazy('Variant status', 'out of stock') else: raise NotImplementedError('Unknown status: %s' % status)
def order_address(request, order_pk, address_type): order = get_object_or_404(Order, pk=order_pk) update_prices = False if address_type == 'shipping': address = order.shipping_address success_msg = pgettext_lazy( 'Dashboard message', 'Updated shipping address') update_prices = True else: address = order.billing_address success_msg = pgettext_lazy( 'Dashboard message', 'Updated billing address') form = AddressForm(request.POST or None, instance=address) if form.is_valid(): updated_address = form.save() if not address: save_address_in_order(order, updated_address, address_type) if update_prices: update_order_prices(order, request.discounts) if not order.is_draft(): order.events.create( user=request.user, type=OrderEvents.UPDATED.value) messages.success(request, success_msg) return redirect('dashboard:order-details', order_pk=order_pk) ctx = {'order': order, 'address_type': address_type, 'form': form} return TemplateResponse(request, 'dashboard/order/address_form.html', ctx)
def add_variant_to_order(request, order_pk): """Add variant in given quantity to an order.""" order = get_object_or_404(Order.objects.drafts(), pk=order_pk) taxes = get_taxes_for_address(order.shipping_address) form = AddVariantToOrderForm( request.POST or None, order=order, discounts=request.discounts, taxes=taxes) status = 200 if form.is_valid(): msg_dict = { 'quantity': form.cleaned_data.get('quantity'), 'variant': form.cleaned_data.get('variant')} try: with transaction.atomic(): form.save() msg = pgettext_lazy( 'Dashboard message related to an order', 'Added %(quantity)d x %(variant)s') % msg_dict messages.success(request, msg) except InsufficientStock: msg = pgettext_lazy( 'Dashboard message related to an order', 'Insufficient stock: could not add %(quantity)d x %(variant)s' ) % msg_dict messages.warning(request, msg) return redirect('dashboard:order-details', order_pk=order_pk) elif form.errors: status = 400 ctx = {'order': order, 'form': form} template = 'dashboard/order/modal/add_variant_to_order.html' return TemplateResponse(request, template, ctx, status=status)
def order_customer_edit(request, order_pk): order = get_object_or_404(Order.objects.drafts(), pk=order_pk) form = OrderCustomerForm(request.POST or None, instance=order) status = 200 if form.is_valid(): form.save() update_order_prices(order, request.discounts) user_email = form.cleaned_data.get('user_email') user = form.cleaned_data.get('user') if user_email: msg = pgettext_lazy( 'Dashboard message', '%s email assigned to an order') % user_email elif user: msg = pgettext_lazy( 'Dashboard message', '%s user assigned to an order') % user else: msg = pgettext_lazy( 'Dashboard message', 'Guest user assigned to an order') messages.success(request, msg) return redirect('dashboard:order-details', order_pk=order_pk) elif form.errors: status = 400 ctx = {'order': order, 'form': form} return TemplateResponse( request, 'dashboard/order/modal/edit_customer.html', ctx, status=status)
def read_action(article, action_name, with_text=True, popover_direction=None): if action_name.startswith('is_'): return { 'with_text': with_text, 'popover_direction': popover_direction or 'top', 'action_name': action_name, 'action_data': Read.status_data.get(action_name), 'popover_class': '' if with_text else 'popover-tooltip', 'js_func': "toggle_status(event, '{0}', '{1}')".format( article.id, action_name) } return { 'with_text': with_text, 'popover_direction': popover_direction or 'top', 'action_name': action_name, 'action_data': { 'do_title': _(u'Convert to full-text'), 'undo_title': _(u'Switch back to original'), 'do_label': _(u'Convert to full-text'), 'undo_label': _(u'View original'), 'status_label': _(u'converted'), 'do_icon': pgettext_lazy(u'awesome-font icon name', u'exchange'), 'undo_icon': pgettext_lazy(u'awesome-font icon name', u'exchange'), }, 'popover_class': '' if with_text else 'popover-tooltip', 'js_func': "switch_view('{0}', '{1}')".format( article.id, action_name) }
def __init__(self, *args, **kwargs): self.product_attributes = [] super(ProductForm, self).__init__(*args, **kwargs) for field_name, field in self.fields.items(): field.widget.attrs['class'] = 'form-control' field = self.fields['is_featured'] field.widget.attrs['class'] = 'styled' field = self.fields['product_class'] field.widget.attrs['class'] = 'form-control bootstrap-select' field = self.fields['available_on'] field.widget.attrs['class'] = 'form-control pickadate-selectors' field = self.fields['name'] field.widget.attrs['placeholder'] = pgettext_lazy( 'Product form placeholder', 'Give your awesome product a name') field = self.fields['categories'] field.widget.attrs['data-placeholder'] = pgettext_lazy( 'Product form placeholder', 'Search') field.widget.attrs['class'] = 'form-control multiselect' field.widget.attrs['multiple'] = 'multiple' field = self.fields['product_tax'] field.widget.attrs['class'] = 'form-control bootstrap-select' product_class = self.instance.product_class self.product_attributes = product_class.product_attributes.all() self.product_attributes = self.product_attributes.prefetch_related( 'values') self.prepare_fields_for_attributes()
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) product_type = self.instance.product_type self.initial['tax_rate'] = ( self.instance.tax_rate or product_type.tax_rate) self.available_attributes = ( product_type.product_attributes.prefetch_related('values').all()) self.prepare_fields_for_attributes() self.fields['collections'].initial = Collection.objects.filter( products__name=self.instance) self.fields['seo_description'] = SeoDescriptionField( extra_attrs={ 'data-bind': self['description'].auto_id, 'data-materialize': self['description'].html_name}) self.fields['seo_title'] = SeoTitleField( extra_attrs={'data-bind': self['name'].auto_id}) self.fields['tax_rate'].choices = get_tax_rate_type_choices() if include_taxes_in_prices(): self.fields['price'].label = pgettext_lazy( 'Currency gross amount', 'Gross price') else: self.fields['price'].label = pgettext_lazy( 'Currency net amount', 'Net price') if not product_type.is_shipping_required: del self.fields['weight'] else: self.fields['weight'].widget.attrs['placeholder'] = ( product_type.weight.value)
def clean(self): parent = self.instance.parent if parent and parent.level >= 2: raise forms.ValidationError( pgettext_lazy( 'Menu item form error', 'Maximum nesting level for menu items equals to 2.'), code='invalid') url = self.cleaned_data.get('url') linked_object = self.cleaned_data.get('linked_object') if url and linked_object: raise forms.ValidationError( pgettext_lazy( 'Menu item form error', 'A single menu item can\'t point to both an internal link ' 'and URL.'), code='invalid') if not url and not linked_object: raise forms.ValidationError( pgettext_lazy( 'Menu item form error', 'A single menu item must point to an internal link or ' 'URL.'), code='invalid') return self.cleaned_data
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.instance.product.pk: self.fields['price_override'].widget.attrs[ 'placeholder'] = self.instance.product.price.amount self.available_attributes = ( self.instance.product.product_type.variant_attributes.all() .prefetch_related('values')) self.prepare_fields_for_attributes() if include_taxes_in_prices(): self.fields['price_override'].label = pgettext_lazy( 'Override price', 'Selling gross price override') self.fields['cost_price'].label = pgettext_lazy( 'Currency amount', 'Cost gross price') else: self.fields['price_override'].label = pgettext_lazy( 'Override price', 'Selling net price override') self.fields['cost_price'].label = pgettext_lazy( 'Currency amount', 'Cost net price') if not self.instance.product.product_type.is_shipping_required: del self.fields['weight'] else: self.fields['weight'].widget.attrs['placeholder'] = ( getattr(self.instance.product.weight, 'value', None) or self.instance.product.product_type.weight.value)
def get_diet(self): diets = { self.__class__.DIET_VEGAN: pgettext_lazy(context=self.get_gender(), message='Vegan'), self.__class__.DIET_VEGETARIAN: pgettext_lazy(context=self.get_gender(), message='Vegetarian'), self.__class__.DIET_CARNIST: pgettext_lazy(context=self.get_gender(), message='Carnist'), } return diets.get(self.diet)
def diet_choices(gender): return ( # (__class__.DIET_UNKNOWN, _("Unknown")), # ~~~~ TODO: remove this line! (__class__.DIET_VEGAN, pgettext_lazy(context=gender, message="Vegan (eats only plants and fungi)")), (__class__.DIET_VEGETARIAN, pgettext_lazy(context=gender, message="Vegetarian (doesn't eat fish and meat)")), (__class__.DIET_CARNIST, pgettext_lazy(context=gender, message="Carnist (eats animals)")), )
def clean(self): parent = self.instance.parent if parent and parent.level >= 2: raise forms.ValidationError( pgettext_lazy( "Menu item form error", "Maximum nesting level for menu items equals to 2.", ), code="invalid", ) url = self.cleaned_data.get("url") linked_object = self.cleaned_data.get("linked_object") if url and linked_object: raise forms.ValidationError( pgettext_lazy( "Menu item form error", "A single menu item can't point to both an internal link " "and URL.", ), code="invalid", ) if not url and not linked_object: raise forms.ValidationError( pgettext_lazy( "Menu item form error", "A single menu item must point to an internal link or " "URL.", ), code="invalid", ) return self.cleaned_data
def add_variant_to_group(request, order_pk, group_pk): """Add variant in given quantity to an existing or new order group.""" order = get_object_or_404(Order, pk=order_pk) group = get_object_or_404(order.groups.all(), pk=group_pk) form = AddVariantToDeliveryGroupForm( request.POST or None, group=group, discounts=request.discounts) status = 200 if form.is_valid(): msg_dict = { 'quantity': form.cleaned_data.get('quantity'), 'variant': form.cleaned_data.get('variant'), 'group': group} try: with transaction.atomic(): form.save() msg = pgettext_lazy( 'Dashboard message related to a shipment group', 'Added %(quantity)d x %(variant)s to %(group)s') % msg_dict order.create_history_entry(content=msg, user=request.user) messages.success(request, msg) except InsufficientStock: msg = pgettext_lazy( 'Dashboard message related to a shipment group', 'Insufficient stock: could not add %(quantity)d x ' '%(variant)s to %(group)s') % msg_dict messages.warning(request, msg) return redirect('dashboard:order-details', order_pk=order_pk) elif form.errors: status = 400 ctx = {'order': order, 'group': group, 'form': form} template = 'dashboard/order/modal/add_variant_to_group.html' return TemplateResponse(request, template, ctx, status=status)
def order_customer_edit(request, order_pk): order = get_object_or_404(Order.objects.drafts(), pk=order_pk) form = OrderCustomerForm(request.POST or None, instance=order) status = 200 if form.is_valid(): form.save() update_order_prices(order, request.discounts) user_email = form.cleaned_data.get("user_email") user = form.cleaned_data.get("user") if user_email: msg = ( pgettext_lazy("Dashboard message", "%s email assigned to an order") % user_email ) elif user: msg = ( pgettext_lazy("Dashboard message", "%s user assigned to an order") % user ) else: msg = pgettext_lazy("Dashboard message", "Guest user assigned to an order") messages.success(request, msg) return redirect("dashboard:order-details", order_pk=order_pk) elif form.errors: status = 400 ctx = {"order": order, "form": form} return TemplateResponse( request, "dashboard/order/modal/edit_customer.html", ctx, status=status )
def order_address(request, order_pk, address_type): order = get_object_or_404(Order, pk=order_pk) update_prices = False if address_type == "shipping": address = order.shipping_address success_msg = pgettext_lazy("Dashboard message", "Updated shipping address") update_prices = True else: address = order.billing_address success_msg = pgettext_lazy("Dashboard message", "Updated billing address") form = AddressForm(request.POST or None, instance=address) if form.is_valid(): updated_address = form.save() if not address: save_address_in_order(order, updated_address, address_type) if update_prices: update_order_prices(order, request.discounts) if not order.is_draft(): events.order_updated_address_event( order=order, user=request.user, address=address ) messages.success(request, success_msg) return redirect("dashboard:order-details", order_pk=order_pk) ctx = {"order": order, "address_type": address_type, "form": form} return TemplateResponse(request, "dashboard/order/address_form.html", ctx)
def get_display(status): if status == VariantAvailabilityStatus.AVAILABLE: return pgettext_lazy("Variant status", "available") elif status == VariantAvailabilityStatus.OUT_OF_STOCK: return pgettext_lazy("Variant status", "out of stock") else: raise NotImplementedError("Unknown status: %s" % status)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.instance.product.pk: self.fields["price_override"].widget.attrs[ "placeholder" ] = self.instance.product.price.amount qs = self.instance.product.product_type.variant_attributes.all() self.available_attributes = qs.prefetch_related("values") self.prepare_fields_for_attributes() if include_taxes_in_prices(): self.fields["price_override"].label = pgettext_lazy( "Override price", "Selling gross price override" ) self.fields["cost_price"].label = pgettext_lazy( "Currency amount", "Cost gross price" ) else: self.fields["price_override"].label = pgettext_lazy( "Override price", "Selling net price override" ) self.fields["cost_price"].label = pgettext_lazy( "Currency amount", "Cost net price" ) if not self.instance.product.product_type.is_shipping_required: del self.fields["weight"] else: self.fields["weight"].widget.attrs["placeholder"] = ( getattr(self.instance.product.weight, "value", None) or self.instance.product.product_type.weight.value )
def smoking_status_choices(gender): return ( # (__class__.SMOKING_STATUS_UNKNOWN, _("Unknown")), # ~~~~ TODO: remove this line! (__class__.SMOKING_STATUS_NO, pgettext_lazy(context=gender, message="No")), (__class__.SMOKING_STATUS_SOMETIMES, pgettext_lazy(context=gender, message="Sometimes")), (__class__.SMOKING_STATUS_YES, pgettext_lazy(context=gender, message="Yes")), )
def product_image_edit(request, product_pk, img_pk=None): product = get_object_or_404(Product, pk=product_pk) if img_pk: product_image = get_object_or_404(product.images, pk=img_pk) else: product_image = ProductImage(product=product) show_variants = product.product_class.has_variants form = forms.ProductImageForm(request.POST or None, request.FILES or None, instance=product_image) if form.is_valid(): product_image = form.save() if img_pk: msg = pgettext_lazy( 'Dashboard message', 'Updated image %s') % product_image.image.name else: msg = pgettext_lazy( 'Dashboard message', 'Added image %s') % product_image.image.name messages.success(request, msg) success_url = request.POST['success_url'] if is_safe_url(success_url, request.get_host()): return redirect(success_url) ctx = {'form': form, 'product': product, 'product_image': product_image, 'show_variants': show_variants} return TemplateResponse( request, 'dashboard/product/product_image_form.html', ctx)
def variant_edit(request, product_pk, variant_pk=None): product = get_object_or_404(Product.objects.all(), pk=product_pk) form_initial = {} if variant_pk: variant = get_object_or_404(product.variants.all(), pk=variant_pk) else: variant = ProductVariant(product=product) form = forms.ProductVariantForm(request.POST or None, instance=variant, initial=form_initial) attribute_form = forms.VariantAttributeForm(request.POST or None, instance=variant) if all([form.is_valid(), attribute_form.is_valid()]): form.save() attribute_form.save() if variant_pk: msg = pgettext_lazy( 'Dashboard message', 'Updated variant %s') % variant.name else: msg = pgettext_lazy( 'Dashboard message', 'Added variant %s') % variant.name messages.success(request, msg) success_url = request.POST['success_url'] if is_safe_url(success_url, request.get_host()): return redirect(success_url) ctx = {'attribute_form': attribute_form, 'form': form, 'product': product, 'variant': variant} return TemplateResponse( request, 'dashboard/product/variant_form.html', ctx)
def clean(self): super().clean() errors = [] if self.instance.get_total_quantity() == 0: errors.append( forms.ValidationError( pgettext_lazy( "Create draft order form error", "Could not create order without any products", ) ) ) if self.instance.is_shipping_required(): method = self.instance.shipping_method shipping_address = self.instance.shipping_address shipping_not_valid = ( method and shipping_address and shipping_address.country.code not in method.shipping_zone.countries ) # noqa if shipping_not_valid: errors.append( forms.ValidationError( pgettext_lazy( "Create draft order form error", "Shipping method is not valid for chosen shipping " "address", ) ) ) if errors: raise forms.ValidationError(errors) return self.cleaned_data
def address_view(request, order_pk, address_type): order = Order.objects.get(pk=order_pk) if address_type == 'shipping': address = order.shipping_address success_msg = pgettext_lazy( 'Dashboard message', 'Updated shipping address') else: address = order.billing_address success_msg = pgettext_lazy( 'Dashboard message', 'Updated billing address') form = AddressForm(request.POST or None, instance=address) if form.is_valid(): updated_address = form.save() if address is None: if address_type == 'shipping': order.shipping_address = updated_address else: order.billing_address = updated_address order.save() order.create_history_entry(content=success_msg, user=request.user) messages.success(request, success_msg) return redirect('dashboard:order-details', order_pk=order_pk) ctx = {'order': order, 'address_type': address_type, 'form': form} return TemplateResponse(request, 'dashboard/order/address_form.html', ctx)
def clean(self): data = super(ProductClassForm, self).clean() has_variants = self.cleaned_data['has_variants'] product_attr = set(self.cleaned_data['product_attributes']) variant_attr = set(self.cleaned_data['variant_attributes']) if not has_variants and len(variant_attr) > 0: msg = pgettext_lazy( 'Product class form error', 'Product variants are disabled.') self.add_error('variant_attributes', msg) if len(product_attr & variant_attr) > 0: msg = pgettext_lazy( 'Product class form error', 'A single attribute can\'t belong to both a product ' 'and its variant.') self.add_error('variant_attributes', msg) if self.instance.pk: variants_changed = not (self.fields['has_variants'].initial == has_variants) if variants_changed: query = self.instance.products.all() query = query.annotate(variants_counter=Count('variants')) query = query.filter(variants_counter__gt=1) if query.exists(): msg = pgettext_lazy( 'Product class form error', 'Some products of this type have more than ' 'one variant.') self.add_error('has_variants', msg) return data
def fulfill_order_lines(request, order_pk): orders = Order.objects.confirmed().prefetch_related('lines') order = get_object_or_404(orders, pk=order_pk) unfulfilled_lines = order.lines.filter( quantity_fulfilled__lt=F('quantity')) status = 200 form = FulfillmentForm( request.POST or None, order=order, instance=Fulfillment()) FulfillmentLineFormSet = modelformset_factory( FulfillmentLine, form=FulfillmentLineForm, extra=len(unfulfilled_lines), formset=BaseFulfillmentLineFormSet) initial = [ {'order_line': line, 'quantity': line.quantity_unfulfilled} for line in unfulfilled_lines] formset = FulfillmentLineFormSet( request.POST or None, queryset=FulfillmentLine.objects.none(), initial=initial) all_forms_valid = all([line_form.is_valid() for line_form in formset]) if all_forms_valid and form.is_valid(): forms_to_save = [ line_form for line_form in formset if line_form.cleaned_data.get('quantity') > 0] if forms_to_save: fulfillment = form.save() quantity_fulfilled = 0 for line_form in forms_to_save: line = line_form.save(commit=False) line.fulfillment = fulfillment line.save() quantity_fulfilled += line_form.cleaned_data.get('quantity') # update to refresh prefetched lines quantity_fulfilled order = orders.get(pk=order_pk) update_order_status(order) msg = npgettext_lazy( 'Dashboard message related to an order', 'Fulfilled %(quantity_fulfilled)d item', 'Fulfilled %(quantity_fulfilled)d items', 'quantity_fulfilled') % { 'quantity_fulfilled': quantity_fulfilled} order.history.create(content=msg, user=request.user) if form.cleaned_data.get('send_mail'): send_fulfillment_confirmation.delay(order.pk, fulfillment.pk) send_mail_msg = pgettext_lazy( 'Dashboard message related to an order', 'Shipping confirmation email was sent to user' '(%(email)s)') % {'email': order.get_user_current_email()} order.history.create(content=send_mail_msg, user=request.user) else: msg = pgettext_lazy( 'Dashboard message related to an order', 'No items fulfilled') messages.success(request, msg) return redirect('dashboard:order-details', order_pk=order.pk) elif form.errors: status = 400 ctx = { 'form': form, 'formset': formset, 'order': order, 'unfulfilled_lines': unfulfilled_lines} template = 'dashboard/order/fulfillment.html' return TemplateResponse(request, template, ctx, status=status)
def render(self, data, accepted_media_type=None, renderer_context=None): if renderer_context and isinstance(data, dict): data['page_size'] = renderer_context['view'].paginate_by # localization pager data['previous'] = pgettext_lazy('pager_prev', 'Prev') data['next'] = pgettext_lazy('pager_next', 'Next') return super(TicketJSONRenderer, self).render(data, accepted_media_type, renderer_context)
class Meta: verbose_name = pgettext_lazy('Category model', 'category') verbose_name_plural = pgettext_lazy('Category model', 'categories') app_label = 'product'
class Meta: permissions = ( ('view_voucher', pgettext_lazy('Permission description', 'Can view vouchers')), ('edit_voucher', pgettext_lazy('Permission description', 'Can edit vouchers')))
class Meta: verbose_name = pgettext_lazy( 'Product class model', 'product class') verbose_name_plural = pgettext_lazy( 'Product class model', 'product classes') app_label = 'product'
from django.conf import settings from django.template import Library from django.utils.translation import npgettext_lazy, pgettext_lazy from django_prices.templatetags import prices from prices import Money from ...order import events from ...order.models import OrderEvent register = Library() EMAIL_CHOICES = { events.OrderEventsEmails.PAYMENT: pgettext_lazy("Email type", "Payment confirmation"), events.OrderEventsEmails.SHIPPING: pgettext_lazy("Email type", "Shipping confirmation"), events.OrderEventsEmails.FULFILLMENT: pgettext_lazy("Email type", "Fulfillment confirmation"), events.OrderEventsEmails.ORDER: pgettext_lazy("Email type", "Order confirmation"), } def get_money_from_params(amount): """Retrieve the correct money amount from the given object. Money serialization changed at one point, as for now it's serialized as a dict. But we keep those settings for the legacy data. Can be safely removed after migrating to Dashboard 2.0 """
class Sale(models.Model): FIXED = 'fixed' PERCENTAGE = 'percentage' DISCOUNT_TYPE_CHOICES = ( (FIXED, pgettext_lazy('Discount type', settings.DEFAULT_CURRENCY)), (PERCENTAGE, pgettext_lazy('Discount type', '%'))) name = models.CharField( pgettext_lazy('Sale (discount) field', 'name'), max_length=255) type = models.CharField( pgettext_lazy('Sale (discount) field', 'type'), max_length=10, choices=DISCOUNT_TYPE_CHOICES, default=FIXED) value = models.DecimalField( pgettext_lazy('Sale (discount) field', 'value'), max_digits=12, decimal_places=2, default=0) products = models.ManyToManyField( 'product.Product', blank=True, verbose_name=pgettext_lazy('Sale (discount) field', 'products')) categories = models.ManyToManyField( 'product.Category', blank=True, verbose_name=pgettext_lazy('Sale (discount) field', 'categories')) class Meta: app_label = 'discount' permissions = ( ('view_sale', pgettext_lazy('Permission description', 'Can view sales')), ('edit_sale', pgettext_lazy('Permission description', 'Can edit sales'))) def __repr__(self): return 'Sale(name=%r, value=%r, type=%s)' % ( str(self.name), self.value, self.get_type_display()) def __str__(self): return self.name def get_discount(self): if self.type == self.FIXED: discount_price = Price(net=self.value, currency=settings.DEFAULT_CURRENCY) return FixedDiscount(amount=discount_price, name=self.name) elif self.type == self.PERCENTAGE: return percentage_discount(value=self.value, name=self.name) raise NotImplementedError('Unknown discount type') def _product_has_category_discount(self, product, discounted_categories): for category in product.categories.all(): for discounted_category in discounted_categories: if category.is_descendant_of(discounted_category, include_self=True): return True return False def modifier_for_product(self, product): discounted_products = {p.pk for p in self.products.all()} discounted_categories = set(self.categories.all()) if product.pk in discounted_products: return self.get_discount() if self._product_has_category_discount( product, discounted_categories): return self.get_discount() raise NotApplicable( pgettext( 'Voucher not applicable', 'Discount not applicable for this product'))
class Meta: app_label = 'product' verbose_name = pgettext_lazy('Product variant model', 'product variant') verbose_name_plural = pgettext_lazy('Product variant model', 'product variants')
class Product(models.Model, ItemRange, index.Indexed): product_class = models.ForeignKey( ProductClass, related_name='products', verbose_name=pgettext_lazy('Product field', 'product class')) name = models.CharField( pgettext_lazy('Product field', 'name'), max_length=128) description = models.TextField( verbose_name=pgettext_lazy('Product field', 'description')) categories = models.ManyToManyField( Category, verbose_name=pgettext_lazy('Product field', 'categories'), related_name='products') price = PriceField( pgettext_lazy('Product field', 'price'), currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2) available_on = models.DateField( pgettext_lazy('Product field', 'available on'), blank=True, null=True) attributes = HStoreField(pgettext_lazy('Product field', 'attributes'), default={}) updated_at = models.DateTimeField( pgettext_lazy('Product field', 'updated at'), auto_now=True, null=True) is_featured = models.BooleanField( pgettext_lazy('Product field', 'is featured'), default=False) objects = ProductManager() search_fields = [ index.SearchField('name', partial_match=True), index.SearchField('description'), index.FilterField('available_on')] class Meta: app_label = 'product' verbose_name = pgettext_lazy('Product model', 'product') verbose_name_plural = pgettext_lazy('Product model', 'products') def __iter__(self): if not hasattr(self, '__variants'): setattr(self, '__variants', self.variants.all()) return iter(getattr(self, '__variants')) def __repr__(self): class_ = type(self) return '<%s.%s(pk=%r, name=%r)>' % ( class_.__module__, class_.__name__, self.pk, self.name) def __str__(self): return self.name def get_absolute_url(self): return reverse('product:details', kwargs={'slug': self.get_slug(), 'product_id': self.id}) def get_slug(self): return slugify(smart_text(unidecode(self.name))) def is_in_stock(self): return any(variant.is_in_stock() for variant in self) def get_first_category(self): for category in self.categories.all(): if not category.hidden: return category return None def is_available(self): today = datetime.date.today() return self.available_on is None or self.available_on <= today def get_first_image(self): first_image = self.images.first() if first_image: return first_image.image return None def get_attribute(self, pk): return self.attributes.get(smart_text(pk)) def set_attribute(self, pk, value_pk): self.attributes[smart_text(pk)] = smart_text(value_pk) def get_price_range(self, discounts=None, **kwargs): if not self.variants.exists(): price = calculate_discounted_price(self, self.price, discounts, **kwargs) return PriceRange(price, price) else: return super(Product, self).get_price_range( discounts=discounts, **kwargs)
class Meta: verbose_name = pgettext_lazy("Model name", "Decision") verbose_name_plural = pgettext_lazy("Model name", "Decisions")
class Meta(NameModel.Meta): verbose_name = pgettext_lazy("Model name", "Decision maker") verbose_name_plural = pgettext_lazy("Model name", "Decision makers")
class AddToCartForm(forms.Form): """Add-to-cart form. Allows selection of a product variant and quantity. The save method adds it to the cart. """ quantity = QuantityField( label=pgettext_lazy('Add to cart form field label', 'Quantity')) error_messages = { 'not-available': pgettext_lazy('Add to cart form error', 'Sorry. This product is currently not available.'), 'empty-stock': pgettext_lazy('Add to cart form error', 'Sorry. This product is currently out of stock.'), 'variant-does-not-exists': pgettext_lazy('Add to cart form error', 'Oops. We could not find that product.'), 'insufficient-stock': npgettext_lazy('Add to cart form error', 'Only %d remaining in stock.', 'Only %d remaining in stock.') } def __init__(self, *args, **kwargs): self.cart = kwargs.pop('cart') self.product = kwargs.pop('product') self.discounts = kwargs.pop('discounts', ()) super().__init__(*args, **kwargs) def clean(self): """Clean the form. Makes sure the total quantity in cart (taking into account what was already there) does not exceed available quantity. """ cleaned_data = super().clean() quantity = cleaned_data.get('quantity') if quantity is None: return cleaned_data try: product_variant = self.get_variant(cleaned_data) except ObjectDoesNotExist: msg = self.error_messages['variant-does-not-exists'] self.add_error(NON_FIELD_ERRORS, msg) else: cart_line = self.cart.get_line(product_variant) used_quantity = cart_line.quantity if cart_line else 0 new_quantity = quantity + used_quantity try: product_variant.check_quantity(new_quantity) except InsufficientStock as e: remaining = e.item.quantity_available - used_quantity if remaining: msg = self.error_messages['insufficient-stock'] self.add_error('quantity', msg % remaining) else: msg = self.error_messages['empty-stock'] self.add_error('quantity', msg) return cleaned_data def save(self): """Add the selected product variant and quantity to the cart.""" product_variant = self.get_variant(self.cleaned_data) return self.cart.add(variant=product_variant, quantity=self.cleaned_data['quantity']) def get_variant(self, cleaned_data): """Return a product variant that matches submitted values. This allows specialized implementations to select the variant based on multiple fields (like size and color) instead of having a single variant selection widget. """ raise NotImplementedError()
class Meta(NameModel.Meta): verbose_name = pgettext_lazy("Model name", "Condition type") verbose_name_plural = pgettext_lazy("Model name", "Condition types")
class Account(models.Model): ACCOUNT_STATUS_CHOICE = (("open", "Open"), ("close", "Close")) name = models.CharField(pgettext_lazy("Name of Account", "Name"), max_length=64) mail = models.EmailField() phone = PhoneNumberField(null=True) industry = models.CharField( _("Industry Type"), max_length=255, choices=INDCHOICES, blank=True, null=True ) # billing_address = models.ForeignKey( # Address, related_name='account_billing_address', on_delete=models.CASCADE, blank=True, null=True) # shipping_address = models.ForeignKey( # Address, related_name='account_shipping_address', on_delete=models.CASCADE, blank=True, null=True) billing_address_line = models.CharField( _("Address"), max_length=255, blank=True, null=True ) billing_street = models.CharField(_("Street"), max_length=55, blank=True, null=True) billing_city = models.CharField(_("City"), max_length=255, blank=True, null=True) billing_state = models.CharField(_("State"), max_length=255, blank=True, null=True) billing_postcode = models.CharField( _("Post/Zip-code"), max_length=64, blank=True, null=True ) billing_country = models.CharField( max_length=3, choices=COUNTRIES, blank=True, null=True ) website = models.URLField(_("Website"), blank=True, null=True) description = models.TextField(blank=True, null=True) created_by = models.ForeignKey('commons.User', on_delete=models.SET_NULL, null=True) created_on = models.DateTimeField(_("Created on"), auto_now_add=True) is_active = models.BooleanField(default=False) tags = models.ManyToManyField(Tag, blank=True) status = models.CharField(choices=ACCOUNT_STATUS_CHOICE, max_length=64, default="open") lead = models.ForeignKey("crm.Lead", on_delete=models.SET_NULL, null=True,) contact_name = models.CharField(pgettext_lazy("Name of Contact", "Contact Name"), max_length=120) contacts = models.ManyToManyField("crm.Contact",) company = models.ForeignKey('commons.Company', on_delete=models.SET_NULL, null=True, blank=True) def __str__(self): return self.name class Meta: ordering = ["-created_on"] app_label = "crm" def get_complete_address(self): """Concatenates complete address.""" address = "" add_to_address = [ self.billing_street, self.billing_city, self.billing_state, self.billing_postcode, self.get_billing_country_display(), ] address = append_str_to(address, *add_to_address) return address @property def created_on_arrow(self): return arrow.get(self.created_on).humanize() @property def contact_values(self): contacts = list(self.contacts.values_list("id", flat=True)) return ",".join(str(contact) for contact in contacts)
from django.db.models import Q from django.utils.translation import npgettext, pgettext_lazy from django_filters import CharFilter, OrderingFilter from ...core.filters import SortedFilterSet from ...menu.models import Menu, MenuItem MENU_SORT_BY_FIELDS = { 'slug': pgettext_lazy('Menu list sorting option', 'Internal name')} MENU_ITEM_SORT_BY_FIELDS = { 'name': pgettext_lazy('Menu item list sorting option', 'Name')} class MenuFilter(SortedFilterSet): slug = CharFilter( label=pgettext_lazy('Menu list filter label', 'Internal name'), lookup_expr='icontains') sort_by = OrderingFilter( label=pgettext_lazy('Menu list filter label', 'Sort by'), fields=MENU_SORT_BY_FIELDS.keys(), field_labels=MENU_SORT_BY_FIELDS) class Meta: model = Menu fields = [] def get_summary_message(self): counter = self.qs.count() return npgettext( 'Number of matching records in the dashboard menus list',
class LiteInstallButton(InstallButton): install_class = ['lite'] button_class = ['caution'] install_text = pgettext_lazy('install_button', u'Experimental')
class Meta: ordering = ('name', ) verbose_name = pgettext_lazy('Product attribute model', 'product attribute') verbose_name_plural = pgettext_lazy('Product attribute model', 'product attributes')
def payment_error(self, message): self.add_error( None, pgettext_lazy('Payment form error', 'Payment gateway error: %s') % message)
class StockLocation(models.Model): name = models.CharField( pgettext_lazy('Stock location field', 'location'), max_length=100) def __str__(self): return self.name
class OrderNoteForm(forms.Form): message = forms.CharField(label=pgettext_lazy('Order note', 'Note'), widget=forms.Textarea())
class ProductVariant(models.Model, Item): sku = models.CharField( pgettext_lazy('Product variant field', 'SKU'), max_length=32, unique=True) name = models.CharField( pgettext_lazy('Product variant field', 'variant name'), max_length=100, blank=True) price_override = PriceField( pgettext_lazy('Product variant field', 'price override'), currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2, blank=True, null=True) product = models.ForeignKey(Product, related_name='variants') attributes = HStoreField( pgettext_lazy('Product variant field', 'attributes'), default={}) images = models.ManyToManyField( 'ProductImage', through='VariantImage', verbose_name=pgettext_lazy('Product variant field', 'images')) class Meta: app_label = 'product' verbose_name = pgettext_lazy('Product variant model', 'product variant') verbose_name_plural = pgettext_lazy('Product variant model', 'product variants') def __str__(self): return self.name or self.display_variant() def check_quantity(self, quantity): available_quantity = self.get_stock_quantity() if quantity > available_quantity: raise InsufficientStock(self) def get_stock_quantity(self): if not len(self.stock.all()): return 0 return max([stock.quantity_available for stock in self.stock.all()]) def get_price_per_item(self, discounts=None, **kwargs): price = self.price_override or self.product.price price = calculate_discounted_price(self.product, price, discounts, **kwargs) return price def get_absolute_url(self): slug = self.product.get_slug() product_id = self.product.id return reverse('product:details', kwargs={'slug': slug, 'product_id': product_id}) def as_data(self): return { 'product_name': str(self), 'product_id': self.product.pk, 'variant_id': self.pk, 'unit_price': str(self.get_price_per_item().gross)} def is_shipping_required(self): return self.product.product_class.is_shipping_required def is_in_stock(self): return any( [stock.quantity_available > 0 for stock in self.stock.all()]) def get_attribute(self, pk): return self.attributes.get(smart_text(pk)) def set_attribute(self, pk, value_pk): self.attributes[smart_text(pk)] = smart_text(value_pk) def display_variant(self, attributes=None): if attributes is None: attributes = self.product.product_class.variant_attributes.all() values = get_attributes_display_map(self, attributes) if values: return ', '.join( ['%s: %s' % (smart_text(attributes.get(id=int(key))), smart_text(value)) for (key, value) in six.iteritems(values)]) else: return smart_text(self.sku) def display_product(self): return '%s (%s)' % (smart_text(self.product), smart_text(self)) def get_first_image(self): return self.product.get_first_image() def select_stockrecord(self, quantity=1): # By default selects stock with lowest cost price stock = filter( lambda stock: stock.quantity_available >= quantity, self.stock.all()) stock = sorted(stock, key=lambda stock: stock.cost_price, reverse=True) if stock: return stock[0] def get_cost_price(self): stock = self.select_stockrecord() if stock: return stock.cost_price
class Meta: model = Order fields = ['user', 'user_email'] labels = {'user_email': pgettext_lazy('Order customer email', 'Email')}
def display_order_event(order_event: OrderEvent): """Keep the backwards compatibility with the old dashboard and new type of events. The new one is storing enums values instead of raw messages. """ event_type = order_event.type params = order_event.parameters if event_type == events.OrderEvents.PLACED_FROM_DRAFT: return pgettext_lazy("Dashboard message related to an order", "Order placed from draft order") if event_type == events.OrderEvents.PAYMENT_VOIDED: return pgettext_lazy( "Dashboard message related to an order", "Payment was voided by %(user_name)s" % {"user_name": order_event.user}, ) if event_type == events.OrderEvents.PAYMENT_REFUNDED: amount = get_money_from_params(params["amount"]) return pgettext_lazy( "Dashboard message related to an order", "Successfully refunded: %(amount)s" % {"amount": prices.amount(amount)}, ) if event_type == events.OrderEvents.PAYMENT_CAPTURED: amount = get_money_from_params(params["amount"]) return pgettext_lazy( "Dashboard message related to an order", "Successfully captured: %(amount)s" % {"amount": prices.amount(amount)}, ) if event_type == events.OrderEvents.ORDER_MARKED_AS_PAID: return pgettext_lazy("Dashboard message related to an order", "Order manually marked as paid") if event_type == events.OrderEvents.CANCELED: return pgettext_lazy("Dashboard message related to an order", "Order was canceled") if event_type == events.OrderEvents.FULFILLMENT_RESTOCKED_ITEMS: return npgettext_lazy( "Dashboard message related to an order", "We restocked %(quantity)d item", "We restocked %(quantity)d items", number="quantity", ) % { "quantity": params["quantity"] } if event_type == events.OrderEvents.NOTE_ADDED: return pgettext_lazy( "Dashboard message related to an order", "%(user_name)s added note: %(note)s" % { "note": params["message"], "user_name": order_event.user }, ) if event_type == events.OrderEvents.FULFILLMENT_CANCELED: return pgettext_lazy( "Dashboard message", "Fulfillment #%(fulfillment)s canceled by %(user_name)s", ) % { "fulfillment": params["composed_id"], "user_name": order_event.user } if event_type == events.OrderEvents.FULFILLMENT_FULFILLED_ITEMS: return pgettext_lazy("Dashboard message related to an order", "Fulfilled some items") if event_type == events.OrderEvents.PLACED: return pgettext_lazy("Dashboard message related to an order", "Order was placed") if event_type == events.OrderEvents.ORDER_FULLY_PAID: return pgettext_lazy("Dashboard message related to an order", "Order was fully paid") if event_type == events.OrderEvents.EMAIL_SENT: return pgettext_lazy( "Dashboard message related to an order", "%(email_type)s email was sent to the customer " "(%(email)s)", ) % { "email_type": EMAIL_CHOICES[params["email_type"]], "email": params["email"], } if event_type == events.OrderEvents.TRACKING_UPDATED: return pgettext_lazy( "Dashboard message related to an order", "Fulfillment #%(fulfillment)s tracking was updated to" " %(tracking_number)s by %(user_name)s", ) % { "fulfillment": params["composed_id"], "tracking_number": params["tracking_number"], "user_name": order_event.user, } if event_type == events.OrderEvents.DRAFT_CREATED: return pgettext_lazy( "Dashboard message related to an order", "The draft was created by %(user_name)s", ) % { "user_name": order_event.user } if event_type == events.OrderEvents.DRAFT_ADDED_PRODUCTS: return pgettext_lazy("Dashboard message related to an order", "%(user_name)s added some products") % { "user_name": order_event.user } if event_type == events.OrderEvents.DRAFT_REMOVED_PRODUCTS: return pgettext_lazy( "Dashboard message related to an order", "%(user_name)s removed some products", ) % { "user_name": order_event.user } if event_type == events.OrderEvents.OVERSOLD_ITEMS: return pgettext_lazy( "Dashboard message related to an order", "%(user_name)s placed the order by bypassing oversold items", ) % { "user_name": order_event.user } if event_type == events.OrderEvents.UPDATED_ADDRESS: return pgettext_lazy( "Dashboard message related to an order", "The order address was updated by %(user_name)s", ) % { "user_name": order_event.user } if event_type == events.OrderEvents.PAYMENT_FAILED: return pgettext_lazy( "Dashboard message related to an order", "The payment was failed by %(user_name)s", ) % { "user_name": order_event.user } if event_type == events.OrderEvents.OTHER: return order_event.parameters["message"] raise ValueError("Not supported event type: %s" % (event_type))
raise forms.ValidationError( pgettext_lazy('Remove voucher form error', 'This order has no voucher')) return data def remove_voucher(self): decrease_voucher_usage(self.instance.voucher) self.instance.discount_amount = 0 self.instance.discount_name = '' self.instance.translated_discount_name = '' self.instance.voucher = None recalculate_order(self.instance) PAYMENT_STATUS_CHOICES = ( [('', pgettext_lazy('Payment status field value', 'All'))] + ChargeStatus.CHOICES) class PaymentFilterForm(forms.Form): status = forms.ChoiceField(choices=PAYMENT_STATUS_CHOICES) class AddVariantToOrderForm(forms.Form): """Allow adding lines with given quantity to an order.""" variant = AjaxSelect2ChoiceField( queryset=ProductVariant.objects.filter( product__in=Product.objects.published()), fetch_data_url=reverse_lazy('dashboard:ajax-available-variants'), label=pgettext_lazy(
class Voucher(models.Model): APPLY_TO_ONE_PRODUCT = 'one' APPLY_TO_ALL_PRODUCTS = 'all' APPLY_TO_PRODUCT_CHOICES = [ ( APPLY_TO_ONE_PRODUCT, pgettext_lazy('Voucher application', 'Apply to a single item')), ( APPLY_TO_ALL_PRODUCTS, pgettext_lazy( 'Voucher application', 'Apply to all matching products'))] DISCOUNT_VALUE_FIXED = 'fixed' DISCOUNT_VALUE_PERCENTAGE = 'percentage' DISCOUNT_VALUE_TYPE_CHOICES = [ ( DISCOUNT_VALUE_FIXED, pgettext_lazy('Voucher discount type', settings.DEFAULT_CURRENCY)), ( DISCOUNT_VALUE_PERCENTAGE, pgettext_lazy('Voucher discount type', '%'))] PRODUCT_TYPE = 'product' CATEGORY_TYPE = 'category' SHIPPING_TYPE = 'shipping' VALUE_TYPE = 'value' TYPE_CHOICES = [ (VALUE_TYPE, pgettext_lazy('Voucher: discount for', 'All purchases')), (PRODUCT_TYPE, pgettext_lazy('Voucher: discount for', 'One product')), ( CATEGORY_TYPE, pgettext_lazy('Voucher: discount for', 'A category of products')), (SHIPPING_TYPE, pgettext_lazy('Voucher: discount for', 'Shipping'))] type = models.CharField( pgettext_lazy('Voucher field', 'discount for'), max_length=20, choices=TYPE_CHOICES, default=VALUE_TYPE) name = models.CharField( pgettext_lazy('Voucher field', 'name'), max_length=255, null=True, blank=True) code = models.CharField( pgettext_lazy('Voucher field', 'code'), max_length=12, unique=True, db_index=True) usage_limit = models.PositiveIntegerField( pgettext_lazy('Voucher field', 'usage limit'), null=True, blank=True) used = models.PositiveIntegerField(default=0, editable=False) start_date = models.DateField( pgettext_lazy('Voucher field', 'start date'), default=date.today) end_date = models.DateField( pgettext_lazy('Voucher field', 'end date'), null=True, blank=True) discount_value_type = models.CharField( pgettext_lazy('Voucher field', 'discount type'), max_length=10, choices=DISCOUNT_VALUE_TYPE_CHOICES, default=DISCOUNT_VALUE_FIXED) discount_value = models.DecimalField( pgettext_lazy('Voucher field', 'discount value'), max_digits=12, decimal_places=2) # not mandatory fields, usage depends on type product = models.ForeignKey( 'product.Product', blank=True, null=True, verbose_name=pgettext_lazy('Voucher field', 'product'), on_delete=models.CASCADE) category = models.ForeignKey( 'product.Category', blank=True, null=True, verbose_name=pgettext_lazy('Voucher field', 'category'), on_delete=models.CASCADE) apply_to = models.CharField( pgettext_lazy('Voucher field', 'apply to'), max_length=20, blank=True, null=True) limit = PriceField( pgettext_lazy('Voucher field', 'limit'), max_digits=12, decimal_places=2, null=True, blank=True, currency=settings.DEFAULT_CURRENCY) objects = VoucherQueryset.as_manager() @property def is_free(self): return (self.discount_value == Decimal(100) and self.discount_value_type == Voucher.DISCOUNT_VALUE_PERCENTAGE) class Meta: permissions = ( ('view_voucher', pgettext_lazy('Permission description', 'Can view vouchers')), ('edit_voucher', pgettext_lazy('Permission description', 'Can edit vouchers'))) def __str__(self): if self.name: return self.name discount = '%s %s' % ( self.discount_value, self.get_discount_value_type_display()) if self.type == Voucher.SHIPPING_TYPE: if self.is_free: return pgettext('Voucher type', 'Free shipping') else: return pgettext( 'Voucher type', '%(discount)s off shipping') % {'discount': discount} if self.type == Voucher.PRODUCT_TYPE: return pgettext( 'Voucher type', '%(discount)s off %(product)s') % { 'discount': discount, 'product': self.product} if self.type == Voucher.CATEGORY_TYPE: return pgettext( 'Voucher type', '%(discount)s off %(category)s') % { 'discount': discount, 'category': self.category} return pgettext( 'Voucher type', '%(discount)s off') % {'discount': discount} def get_apply_to_display(self): if self.type == Voucher.SHIPPING_TYPE and self.apply_to: return countries.name(self.apply_to) if self.type == Voucher.SHIPPING_TYPE: return pgettext('Voucher', 'Any country') if self.apply_to and self.type in { Voucher.PRODUCT_TYPE, Voucher.CATEGORY_TYPE}: choices = dict(self.APPLY_TO_PRODUCT_CHOICES) return choices[self.apply_to] def get_fixed_discount_for(self, amount): if self.discount_value_type == self.DISCOUNT_VALUE_FIXED: discount_price = Price(net=self.discount_value, currency=settings.DEFAULT_CURRENCY) discount = FixedDiscount( amount=discount_price, name=smart_text(self)) elif self.discount_value_type == self.DISCOUNT_VALUE_PERCENTAGE: discount = percentage_discount( value=self.discount_value, name=smart_text(self)) fixed_discount_value = amount - discount.apply(amount) discount = FixedDiscount( amount=fixed_discount_value, name=smart_text(self)) else: raise NotImplementedError('Unknown discount value type') if discount.amount > amount: return FixedDiscount(amount, name=smart_text(self)) else: return discount def validate_limit(self, value): limit = self.limit if self.limit is not None else value if value < limit: msg = pgettext( 'Voucher not applicable', 'This offer is only valid for orders over %(amount)s.') raise NotApplicable(msg % {'amount': net(limit)}, limit=limit) def get_discount_for_checkout(self, checkout): if self.type == Voucher.VALUE_TYPE: cart_total = checkout.get_subtotal() self.validate_limit(cart_total) return self.get_fixed_discount_for(cart_total) elif self.type == Voucher.SHIPPING_TYPE: if not checkout.is_shipping_required: msg = pgettext( 'Voucher not applicable', 'Your order does not require shipping.') raise NotApplicable(msg) shipping_method = checkout.shipping_method if not shipping_method: msg = pgettext( 'Voucher not applicable', 'Please select a shipping method first.') raise NotApplicable(msg) if (self.apply_to and shipping_method.country_code != self.apply_to): msg = pgettext( 'Voucher not applicable', 'This offer is only valid in %(country)s.') raise NotApplicable( msg % {'country': self.get_apply_to_display()}) cart_total = checkout.get_subtotal() self.validate_limit(cart_total) return self.get_fixed_discount_for(shipping_method.price) elif self.type in (Voucher.PRODUCT_TYPE, Voucher.CATEGORY_TYPE): if self.type == Voucher.PRODUCT_TYPE: prices = list( (item[1] for item in get_product_variants_and_prices( checkout.cart, self.product))) else: prices = list( (item[1] for item in get_category_variants_and_prices( checkout.cart, self.category))) if len(prices) == 0: msg = pgettext( 'Voucher not applicable', 'This offer is only valid for selected items.') raise NotApplicable(msg) if self.apply_to == Voucher.APPLY_TO_ALL_PRODUCTS: discounts = ( self.get_fixed_discount_for(price) for price in prices) discount_total = sum( (discount.amount for discount in discounts), Price(0, currency=settings.DEFAULT_CURRENCY)) return FixedDiscount(discount_total, smart_text(self)) else: product_total = sum( prices, Price(0, currency=settings.DEFAULT_CURRENCY)) return self.get_fixed_discount_for(product_total) else: raise NotImplementedError('Unknown discount type')
class Paginator(DjangoPaginator): template_name = 'django_listing/paginator.html' has_page_info = True has_editable_page_info = False page_info_tpl = pgettext_lazy('paginator', 'Page {page_number} of {nb_pages}') has_row_info = False row_info_tpl = pgettext_lazy('paginator', '{row_first}-{row_last} of {nb_rows}') has_prev_next = True prev_text = pgettext_lazy('paginator', 'Previous') next_text = pgettext_lazy('paginator', 'Next') has_first_last = False first_text = pgettext_lazy('paginator', 'First') last_text = pgettext_lazy('paginator', 'Last') fast_page_step = 0 fast_page_prev_tpl = '-{step}' fast_page_next_tpl = '+{step}' page_scale_size = 0 page_scale_ellipsis = 0 hide_disabled_buttons = False hide_single_page = False parts_order = ('first,fastprev,prev,pageinfo,rowinfo,scale,' 'next,fastnext,last;gotopage') has_goto_page = False goto_page_tpl = pgettext_lazy('paginator', 'Go to page {goto_form}') in_footer = False theme_first_last_has_icon = True theme_first_last_has_text = True theme_first_icon = 'listing-icon-to-start-1' theme_last_icon = 'listing-icon-to-end-1' theme_fast_page_has_icon = True theme_fast_page_has_text = True theme_fast_prev_icon = 'listing-icon-fast-backward' theme_fast_next_icon = 'listing-icon-fast-forward' theme_prev_next_has_icon = True theme_prev_next_has_text = True theme_prev_icon = 'listing-icon-left-dir' theme_next_icon = 'listing-icon-right-dir' theme_button_a_class = 'page-link' theme_button_li_class = 'page-item' theme_button_text_class = 'button-text' def __init__(self, listing, object_list, per_page, orphans=0, allow_empty_first_page=True): super().__init__(object_list, per_page, orphans, allow_empty_first_page) self.listing = listing for k in PAGINATOR_PARAMS_KEYS: listing_key = 'paginator_' + k if hasattr(listing, listing_key): setattr(self, k, getattr(listing, listing_key)) if isinstance(self.parts_order, str): self.parts_order = self.parts_order.replace(' ', '') self.parts_order = list( map(lambda s: s.split(','), self.parts_order.split(';'))) def get_context(self): get_url = self.listing.get_url page = self.listing.current_page fast_page_prev = max(1, page.number - self.fast_page_step) fast_page_next = min(self.num_pages, page.number + self.fast_page_step) beginning_ellipsis_pages = [] beginning_ellipsis_display = True ending_ellipsis_pages = [] ending_ellipsis_display = True if self.page_scale_size: scale_range_min = max(1, page.number - int(self.page_scale_size / 2)) scale_range_max = min(scale_range_min + self.page_scale_size - 1, self.num_pages) if self.page_scale_ellipsis: beginning_ellipsis_pages = [ (p, get_url(page=p), p == page.number) for p in range( 1, min(self.page_scale_ellipsis + 1, scale_range_min)) ] if scale_range_min < self.page_scale_ellipsis + 2: beginning_ellipsis_display = False if scale_range_max == self.num_pages: scale_range_min = max( 1, self.num_pages - self.page_scale_size) else: ending_ellipsis_range_min = max( scale_range_max + 1, self.num_pages - self.page_scale_ellipsis + 1) if ending_ellipsis_range_min == scale_range_max + 1: beginning_ellipsis_display = False ending_ellipsis_range_max = self.num_pages ending_ellipsis_pages = [ (p, get_url(page=p), p == page.number) for p in range(ending_ellipsis_range_min, ending_ellipsis_range_max + 1) ] scale_pages = [(p, get_url(page=p), p == page.number) for p in range(scale_range_min, scale_range_max + 1) ] else: scale_pages = [] if self.has_editable_page_info or self.has_goto_page: goto_form = '<form class="goto-page">' goto_form += self.listing.get_hiddens_html(without='page') goto_form += ('<input type="text" ' 'value="{val}" ' 'name="page{suffix}">'.format( val=page.number, suffix=self.listing.suffix)) goto_form += '</form>' else: goto_form = '' if self.has_editable_page_info: page_number = goto_form else: page_number = page.number nb_page_rows = page.end_index() - page.start_index() + 1 return RenderContext( first_page_url=get_url(page=1), last_page_url=get_url(page=self.num_pages), prev_page_url=get_url( page=page.previous_page_number() if page.number > 1 else 1), next_page_url=get_url(page=page.next_page_number( ) if page.number < self.num_pages else self.num_pages), fast_page_prev_url=get_url(page=fast_page_prev), fast_page_next_url=get_url(page=fast_page_next), fast_page_prev_text=self.fast_page_prev_tpl.format( step=self.fast_page_step), fast_page_next_text=self.fast_page_next_tpl.format( step=self.fast_page_step), scale_pages=scale_pages, beginning_ellipsis_pages=beginning_ellipsis_pages, beginning_ellipsis_display=beginning_ellipsis_display, ending_ellipsis_pages=ending_ellipsis_pages, ending_ellipsis_display=ending_ellipsis_display, nb_page_rows=nb_page_rows, page_info=self.page_info_tpl.format(page_number=page_number, nb_pages=self.num_pages), row_info=self.row_info_tpl.format(row_first=page.start_index(), row_last=page.end_index(), nb_rows=self.count, nb_page_rows=nb_page_rows), goto_page=self.goto_page_tpl.format(goto_form=goto_form), show_paginator_single_page=(self.num_pages > 1 or not self.hide_single_page), current_page=page, paginator=self, )
from django import forms from django.utils.translation import npgettext, pgettext_lazy from django_filters import ( CharFilter, ChoiceFilter, ModelMultipleChoiceFilter, OrderingFilter, RangeFilter, ) from ...core.filters import SortedFilterSet from ...product.models import Attribute, Category, Product, ProductType from ..widgets import MoneyRangeWidget PRODUCT_SORT_BY_FIELDS = { "name": pgettext_lazy("Product list sorting option", "name"), "price_amount": pgettext_lazy("Product type list sorting option", "price"), } PRODUCT_ATTRIBUTE_SORT_BY_FIELDS = { "name": pgettext_lazy("Product attribute list sorting option", "name") } PRODUCT_TYPE_SORT_BY_FIELDS = { "name": pgettext_lazy("Product type list sorting option", "name") } PUBLISHED_CHOICES = ( ("1", pgettext_lazy("Is publish filter choice", "Published")), ("0", pgettext_lazy("Is publish filter choice", "Not published")), )
class AddToCheckoutForm(forms.Form): """Add-to-checkout form. Allows selection of a product variant and quantity. The save method adds it to the checkout. """ quantity = QuantityField( label=pgettext_lazy("Add to checkout form field label", "Quantity") ) error_messages = { "not-available": pgettext_lazy( "Add to checkout form error", "Sorry. This product is currently not available.", ), "empty-stock": pgettext_lazy( "Add to checkout form error", "Sorry. This product is currently out of stock.", ), "variant-too-many-in-checkout": pgettext_lazy( "Add to checkout form error", "Sorry. You can't add more than %d times this item.", ), "variant-does-not-exists": pgettext_lazy( "Add to checkout form error", "Oops. We could not find that product." ), "insufficient-stock": npgettext_lazy( "Add to checkout form error", "Only %d remaining in stock.", "Only %d remaining in stock.", ), } def __init__(self, *args, **kwargs): self.checkout = kwargs.pop("checkout") self.product = kwargs.pop("product") self.discounts = kwargs.pop("discounts", ()) self.country = kwargs.pop("country", {}) self.extensions = kwargs.pop("extensions", None) super().__init__(*args, **kwargs) def add_error_i18n(self, field, error_name, fmt: Any = tuple()): self.add_error(field, self.error_messages[error_name] % fmt) def clean(self): """Clean the form. Makes sure the total quantity in checkout (taking into account what was already there) does not exceed available quantity. """ cleaned_data = super().clean() quantity = cleaned_data.get("quantity") if quantity is None: return cleaned_data variant = self.get_variant(cleaned_data) if variant is None: self.add_error_i18n(NON_FIELD_ERRORS, "variant-does-not-exists") else: line = self.checkout.get_line(variant) used_quantity = line.quantity if line else 0 new_quantity = quantity + used_quantity if new_quantity > settings.MAX_CHECKOUT_LINE_QUANTITY: self.add_error_i18n( "quantity", "variant-too-many-in-checkout", settings.MAX_CHECKOUT_LINE_QUANTITY, ) return cleaned_data try: variant.check_quantity(new_quantity) except InsufficientStock as e: remaining = e.item.quantity_available - used_quantity if remaining: self.add_error_i18n("quantity", "insufficient-stock", remaining) else: self.add_error_i18n("quantity", "empty-stock") return cleaned_data def save(self): """Add the selected product variant and quantity to the checkout.""" from .utils import add_variant_to_checkout variant = self.get_variant(self.cleaned_data) quantity = self.cleaned_data["quantity"] add_variant_to_checkout(self.checkout, variant, quantity) def get_variant(self, cleaned_data): """Return a product variant that matches submitted values. This allows specialized implementations to select the variant based on multiple fields (like size and color) instead of having a single variant selection widget. """ raise NotImplementedError()
class Meta: ordering = ("-last_change", ) permissions = (( CheckoutPermissions.MANAGE_CHECKOUTS.codename, pgettext_lazy("Permission description", "Manage checkouts"), ), )
return _("Not available") def get_keyname(instance): if hasattr(instance, "key_name"): keyname = instance.key_name return keyname return _("Not available") def get_power_state(instance): return POWER_STATES.get(getattr(instance, "OS-EXT-STS:power_state", 0), '') STATUS_DISPLAY_CHOICES = ( ("deleted", pgettext_lazy("Current status of an Instance", u"Deleted")), ("active", pgettext_lazy("Current status of an Instance", u"Active")), ("shutoff", pgettext_lazy("Current status of an Instance", u"Shutoff")), ("suspended", pgettext_lazy("Current status of an Instance", u"Suspended")), ("paused", pgettext_lazy("Current status of an Instance", u"Paused")), ("error", pgettext_lazy("Current status of an Instance", u"Error")), ("resize", pgettext_lazy("Current status of an Instance", u"Resize/Migrate")), ("verify_resize", pgettext_lazy("Current status of an Instance", u"Confirm or Revert Resize/Migrate")), ("revert_resize", pgettext_lazy( "Current status of an Instance", u"Revert Resize/Migrate")), ("reboot", pgettext_lazy("Current status of an Instance", u"Reboot")), ("hard_reboot", pgettext_lazy("Current status of an Instance", u"Hard Reboot")),
class EditNetwork(policy.PolicyTargetMixin, tables.LinkAction): name = "update" verbose_name = _("Edit Network") url = "horizon:admin:networks:update" classes = ("ajax-modal", ) icon = "pencil" policy_rules = (("network", "update_network"), ) # def _get_subnets(network): # cidrs = [subnet.get('cidr') for subnet in network.subnets] # return ','.join(cidrs) DISPLAY_CHOICES = ( ("UP", pgettext_lazy("Admin state of a Network", u"UP")), ("DOWN", pgettext_lazy("Admin state of a Network", u"DOWN")), ) class NetworksTable(tables.DataTable): tenant = tables.Column("tenant_name", verbose_name=_("Project")) name = tables.Column("name_or_id", verbose_name=_("Network Name"), link='horizon:admin:networks:detail') subnets = tables.Column( project_tables.get_subnets, verbose_name=_("Subnets Associated"), ) num_agents = tables.Column("num_agents", verbose_name=_("DHCP Agents")) shared = tables.Column("shared",
class UnreviewedInstallButton(InstallButton): install_class = ['unreviewed'] install_text = pgettext_lazy('install_button', u'Not Reviewed') button_class = 'download caution'.split()