class SavedView(ModelFormSetView): model = get_model('basket', 'line') basket_model = get_model('basket', 'basket') formset_class = SavedLineFormSet form_class = SavedLineForm factory_kwargs = {'extra': 0, 'can_delete': True} def get(self, request, *args, **kwargs): return redirect('basket:summary') def get_queryset(self): try: saved_basket = self.basket_model.saved.get(owner=self.request.user) saved_basket.strategy = self.request.strategy return saved_basket.all_lines() except self.basket_model.DoesNotExist: return [] def get_success_url(self): return safe_referrer(self.request, 'basket:summary') def get_formset_kwargs(self): kwargs = super(SavedView, self).get_formset_kwargs() kwargs['prefix'] = 'saved' kwargs['basket'] = self.request.basket kwargs['strategy'] = self.request.strategy return kwargs def formset_valid(self, formset): offers_before = self.request.basket.applied_offers() is_move = False for form in formset: if form.cleaned_data.get('move_to_basket', False): is_move = True msg = render_to_string('basket/messages/line_restored.html', {'line': form.instance}) messages.info(self.request, msg, extra_tags='safe noicon') real_basket = self.request.basket real_basket.merge_line(form.instance) if is_move: # As we're changing the basket, we need to check if it qualifies # for any new offers. BasketMessageGenerator().apply_messages(self.request, offers_before) response = redirect(self.get_success_url()) else: response = super(SavedView, self).formset_valid(formset) return response def formset_invalid(self, formset): messages.error( self.request, '\n'.join(error for ed in formset.errors for el in ed.values() for error in el)) return redirect_to_referrer(self.request, 'basket:summary')
def process(self): """ Process the file upload and add products to the range """ all_ids = set(self.extract_ids()) products = self.range.all_products() existing_skus = products.values_list('stockrecords__partner_sku', flat=True) existing_skus = set(filter(bool, existing_skus)) existing_upcs = products.values_list('upc', flat=True) existing_upcs = set(filter(bool, existing_upcs)) existing_ids = existing_skus.union(existing_upcs) new_ids = all_ids - existing_ids Product = get_model('catalogue', 'Product') products = Product._default_manager.filter( models.Q(stockrecords__partner_sku__in=new_ids) | models.Q(upc__in=new_ids)) for product in products: self.range.add_product(product) # Processing stats found_skus = products.values_list('stockrecords__partner_sku', flat=True) found_skus = set(filter(bool, found_skus)) found_upcs = set(filter(bool, products.values_list('upc', flat=True))) found_ids = found_skus.union(found_upcs) missing_ids = new_ids - found_ids dupes = set(all_ids).intersection(existing_ids) self.mark_as_processed(products.count(), len(missing_ids), len(dupes)) return products
def get_default_review_status(): ProductReview = get_model('reviews', 'ProductReview') if settings.WSHOP_MODERATE_REVIEWS: return ProductReview.FOR_MODERATION return ProductReview.APPROVED
def add_product(self, product, display_order=None): """ Add product to the range When adding product that is already in the range, prevent re-adding it. If display_order is specified, update it. Default display_order for a new product in the range is 0; this puts the product at the top of the list. """ initial_order = display_order or 0 RangeProduct = get_model('offer', 'RangeProduct') relation, __ = RangeProduct.objects.get_or_create( range=self, product=product, defaults={'display_order': initial_order}) if (display_order is not None and relation.display_order != display_order): relation.display_order = display_order relation.save() # Remove product from excluded products if it was removed earlier and # re-added again, thus it returns back to the range product list. if product.id in self._excluded_product_ids(): self.excluded_products.remove(product) self.invalidate_cached_ids()
class ConditionFactory(factory.DjangoModelFactory): type = get_model('offer', 'Condition').COUNT value = 10 range = factory.SubFactory(RangeFactory) class Meta: model = get_model('offer', 'Condition')
class VoucherRemoveView(View): voucher_model = get_model('voucher', 'voucher') remove_signal = voucher_removal http_method_names = ['post'] def post(self, request, *args, **kwargs): response = redirect('basket:summary') voucher_id = kwargs['pk'] if not request.basket.id: # Hacking attempt - the basket must be saved for it to have # a voucher in it. return response try: voucher = request.basket.vouchers.get(id=voucher_id) except ObjectDoesNotExist: messages.error(request, _("No voucher found with id '%s'") % voucher_id) else: request.basket.vouchers.remove(voucher) self.remove_signal.send(sender=self, basket=request.basket, voucher=voucher) messages.info(request, _("Voucher '%s' removed from basket") % voucher.code) return response
class BenefitFactory(factory.DjangoModelFactory): type = get_model('offer', 'Benefit').PERCENTAGE value = 10 max_affected_items = None range = factory.SubFactory(RangeFactory) class Meta: model = get_model('offer', 'Benefit')
def products(self, create, extracted, **kwargs): if not create or not extracted: return RangeProduct = get_model('offer', 'RangeProduct') for product in extracted: RangeProduct.objects.create(product=product, range=self)
def get_site_offers(self): """ Return site offers that are available to all users """ ConditionalOffer = get_model('offer', 'ConditionalOffer') qs = ConditionalOffer.active.filter(offer_type=ConditionalOffer.SITE) # Using select_related with the condition/benefit ranges doesn't seem # to work. I think this is because both the related objects have the # FK to range with the same name. return qs.select_related('condition', 'benefit')
def remove_product(self, product): """ Remove product from range. To save on queries, this function does not check if the product is in fact in the range. """ RangeProduct = get_model('offer', 'RangeProduct') RangeProduct.objects.filter(range=self, product=product).delete() # Making sure product will be excluded from range products list by adding to # respective field. Otherwise, it could be included as a product from included # category or etc. self.excluded_products.add(product) # Invalidating cached property value with list of IDs of already excluded products. self.invalidate_cached_ids()
def add_new(self): """Add a new voucher to this set""" Voucher = get_model('voucher', 'Voucher') code = get_unused_code(length=self.code_length) voucher = Voucher.objects.create(name=self.name, code=code, voucher_set=self, usage=Voucher.SINGLE_USE, start_datetime=self.start_datetime, end_datetime=self.end_datetime) if self.offer: voucher.offers.add(self.offer) return voucher
def _validate_option(self, value, valid_values=None): if not isinstance(value, get_model('catalogue', 'AttributeOption')): raise ValidationError( _("Must be an AttributeOption model object instance")) if not value.pk: raise ValidationError(_("AttributeOption has not been saved yet")) if valid_values is None: valid_values = self.option_group.options.values_list('option', flat=True) if value.option not in valid_values: raise ValidationError( _("%(enum)s is not a valid choice for %(attr)s") % { 'enum': value, 'attr': self })
class RequestFactory(BaseRequestFactory): Basket = get_model('basket', 'basket') selector = get_class('partner.strategy', 'Selector')() def request(self, user=None, **request): request = super(RequestFactory, self).request(**request) request.user = user or AnonymousUser() request.session = SessionStore() request._messages = FallbackStorage(request) request.basket = self.Basket() request.basket_hash = None strategy = self.selector.strategy(request=request, user=request.user) request.strategy = request.basket.strategy = strategy return request
class Meta: model = get_model('order', 'shippingaddress') fields = [ 'title', 'first_name', 'last_name', 'line1', 'line2', 'line3', 'line4', 'state', 'postcode', 'country', 'phone_number', 'notes', ]
def products(self): """ Return a queryset of products in this offer """ Product = get_model('catalogue', 'Product') if not self.has_products: return Product.objects.none() cond_range = self.condition.range if cond_range.includes_all_products: # Return ALL the products queryset = Product.browsable else: queryset = cond_range.all_products() return queryset.filter(is_discountable=True).exclude( structure=Product.CHILD)
def _validate_url(self, value): try: resolve(value) except Http404: # We load flatpages here as it causes a circular reference problem # sometimes. FlatPages is None if not installed FlatPage = get_model('flatpages', 'FlatPage') if FlatPage is not None: try: FlatPage.objects.get(url=value) except FlatPage.DoesNotExist: self.is_local_url = True else: return raise ValidationError(_('The URL "%s" does not exist') % value) else: self.is_local_url = True
def save_value(self, product, value): # noqa: C901 too complex ProductAttributeValue = get_model('catalogue', 'ProductAttributeValue') try: value_obj = product.attribute_values.get(attribute=self) except ProductAttributeValue.DoesNotExist: # FileField uses False for announcing deletion of the file # not creating a new value delete_file = self.is_file and value is False if value is None or value == '' or delete_file: return value_obj = ProductAttributeValue.objects.create(product=product, attribute=self) if self.is_file: self._save_file(value_obj, value) elif self.is_multi_option: self._save_multi_option(value_obj, value) else: self._save_value(value_obj, value)
class RequestFactory(BaseRequestFactory): Basket = get_model('basket', 'basket') selector = get_class('partner.strategy', 'Selector')() def request(self, user=None, basket=None, **request): request = super(RequestFactory, self).request(**request) request.user = user or AnonymousUser() request.session = SessionStore() request._messages = FallbackStorage(request) # Mimic basket middleware request.strategy = self.selector.strategy(request=request, user=request.user) request.basket = basket or self.Basket() request.basket.strategy = request.strategy request.basket_hash = Signer().sign(basket.pk) if basket else None request.cookies_to_delete = [] return request
def all_products(self): """ Return a queryset containing all the products in the range This includes included_products plus the products contained in the included classes and categories, minus the products in excluded_products. """ if self.proxy: return self.proxy.all_products() Product = get_model("catalogue", "Product") if self.includes_all_products: # Filter out child products return Product.browsable.all() return Product.objects.filter( Q(id__in=self._included_product_ids()) | Q(product_class_id__in=self._class_ids()) | Q(productcategory__category_id__in=self._category_ids()) ).exclude(id__in=self._excluded_product_ids()).distinct()
# -*- coding: utf-8 -*- from django.core.management.base import BaseCommand from wshop.core.loading import get_model Product = get_model('catalogue', 'Product') class Command(BaseCommand): help = """Update the denormalised reviews average on all Product instances. Should only be necessary when changing to e.g. a weight-based rating.""" def handle(self, *args, **options): # Iterate over all Products (not just ones with reviews) products = Product.objects.all() for product in products: product.update_rating() self.stdout.write('Successfully updated %s products\n' % products.count())
import re from calendar import monthrange from datetime import date from django import forms from django.core.exceptions import ImproperlyConfigured from django.utils.translation import ugettext_lazy as _ from wshop.core.loading import get_class, get_model from wshop.forms.mixins import PhoneNumberMixin from . import bankcards Country = get_model('address', 'Country') BillingAddress = get_model('order', 'BillingAddress') Bankcard = get_model('payment', 'Bankcard') AbstractAddressForm = get_class('address.forms', 'AbstractAddressForm') # List of card names for all the card types supported in payment.bankcards VALID_CARDS = set([card_type[0] for card_type in bankcards.CARD_TYPES]) class BankcardNumberField(forms.CharField): def __init__(self, *args, **kwargs): _kwargs = { 'max_length': 20, 'widget': forms.TextInput(attrs={'autocomplete': 'off'}), 'label': _("Card number") } if 'types' in kwargs: self.accepted_cards = set(kwargs.pop('types'))
from django.utils.translation import ugettext_lazy as _ from wshop.core.loading import get_class, get_model ReportGenerator = get_class('dashboard.reports.reports', 'ReportGenerator') ReportCSVFormatter = get_class('dashboard.reports.reports', 'ReportCSVFormatter') ReportHTMLFormatter = get_class('dashboard.reports.reports', 'ReportHTMLFormatter') ProductRecord = get_model('analytics', 'ProductRecord') UserRecord = get_model('analytics', 'UserRecord') class ProductReportCSVFormatter(ReportCSVFormatter): filename_template = 'conditional-offer-performance.csv' def generate_csv(self, response, products): writer = self.get_csv_writer(response) header_row = [_('Product'), _('Views'), _('Basket additions'), _('Purchases')] writer.writerow(header_row) for record in products: row = [record.product, record.num_views, record.num_basket_additions, record.num_purchases] writer.writerow(row)
from django.core import exceptions from django.db import IntegrityError from wshop.core.loading import get_model Benefit = get_model('offer', 'Benefit') Condition = get_model('offer', 'Condition') Range = get_model('offer', 'Range') def _class_path(klass): return '%s.%s' % (klass.__module__, klass.__name__) def create_range(range_class): """ Create a custom range instance from the passed range class This function creates the appropriate database record for this custom range, including setting the class path for the custom proxy class. """ if not hasattr(range_class, 'name'): raise exceptions.ValidationError( "A custom range must have a name attribute") # Ensure range name is text (not ugettext wrapper) if range_class.name.__class__.__name__ == '__proxy__': raise exceptions.ValidationError( "Custom ranges must have text names (not ugettext proxies)") try:
class Meta: model = get_model('payment', 'Source')
from django import forms from django.conf import settings from wshop.core.loading import get_model from wshop.forms.mixins import PhoneNumberMixin UserAddress = get_model('address', 'useraddress') class AbstractAddressForm(forms.ModelForm): def __init__(self, *args, **kwargs): """ Set fields in WSHOP_REQUIRED_ADDRESS_FIELDS as required. """ super(AbstractAddressForm, self).__init__(*args, **kwargs) field_names = (set(self.fields) & set(settings.WSHOP_REQUIRED_ADDRESS_FIELDS)) for field_name in field_names: self.fields[field_name].required = True class UserAddressForm(PhoneNumberMixin, AbstractAddressForm): class Meta: model = UserAddress fields = [ 'title', 'first_name', 'last_name', 'line1', 'line2', 'line3',
from django.conf import settings from django.urls import reverse from django.utils.six.moves import http_client from wshop.core.loading import get_model from wshop.apps.order.models import (Order, OrderNote, PaymentEvent, PaymentEventType) from wshop.test.factories import PartnerFactory, ShippingAddressFactory from wshop.test.factories import create_order, create_basket from wshop.test.testcases import WebTestCase from wshop.test.factories import SourceTypeFactory Basket = get_model('basket', 'Basket') Partner = get_model('partner', 'Partner') ShippingAddress = get_model('order', 'ShippingAddress') class TestOrderListDashboard(WebTestCase): is_staff = True def test_redirects_to_detail_page(self): order = create_order() page = self.get(reverse('dashboard:order-list')) form = page.forms['search_form'] form['order_number'] = order.number response = form.submit() self.assertEqual(http_client.FOUND, response.status_code) def test_downloads_to_csv_without_error(self): address = ShippingAddressFactory() create_order(shipping_address=address)
from django.conf.urls import url from wshop.core.application import Application from wshop.core.loading import get_class, get_model KeywordPromotion = get_model('promotions', 'KeywordPromotion') PagePromotion = get_model('promotions', 'PagePromotion') class PromotionsApplication(Application): name = 'promotions' home_view = get_class('promotions.views', 'HomeView') record_click_view = get_class('promotions.views', 'RecordClickView') def get_urls(self): urls = [ url(r'page-redirect/(?P<page_promotion_id>\d+)/$', self.record_click_view.as_view(model=PagePromotion), name='page-click'), url(r'keyword-redirect/(?P<keyword_promotion_id>\d+)/$', self.record_click_view.as_view(model=KeywordPromotion), name='keyword-click'), url(r'^$', self.home_view.as_view(), name='home'), ] return self.post_process_urls(urls) application = PromotionsApplication()
from django import template from wshop.core.loading import get_model register = template.Library() Category = get_model('catalogue', 'category') @register.simple_tag(name="category_tree") def get_annotated_list(depth=None, parent=None): """ Gets an annotated list from a tree branch. Borrows heavily from treebeard's get_annotated_list """ # 'depth' is the backwards-compatible name for the template tag, # 'max_depth' is the better variable name. max_depth = depth annotated_categories = [] start_depth, prev_depth = (None, None) if parent: categories = parent.get_descendants() if max_depth is not None: max_depth += parent.get_depth() else: categories = Category.get_tree() info = {} for node in categories:
class Meta: model = get_model('payment', 'Transaction')
from wshop.core.loading import get_model Notification = get_model('customer', 'Notification') def notifications(request): ctx = {} if getattr(request, 'user', None) and request.user.is_authenticated: num_unread = Notification.objects.filter(recipient=request.user, date_read=None).count() ctx['num_unread_notifications'] = num_unread return ctx