Пример #1
0
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')
Пример #2
0
    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
Пример #3
0
def get_default_review_status():
    ProductReview = get_model('reviews', 'ProductReview')

    if settings.WSHOP_MODERATE_REVIEWS:
        return ProductReview.FOR_MODERATION

    return ProductReview.APPROVED
Пример #4
0
    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()
Пример #5
0
class ConditionFactory(factory.DjangoModelFactory):
    type = get_model('offer', 'Condition').COUNT
    value = 10
    range = factory.SubFactory(RangeFactory)

    class Meta:
        model = get_model('offer', 'Condition')
Пример #6
0
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
Пример #7
0
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')
Пример #8
0
    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)
Пример #9
0
 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')
Пример #10
0
 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()
Пример #11
0
    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
Пример #12
0
 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
             })
Пример #13
0
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
Пример #14
0
 class Meta:
     model = get_model('order', 'shippingaddress')
     fields = [
         'title',
         'first_name',
         'last_name',
         'line1',
         'line2',
         'line3',
         'line4',
         'state',
         'postcode',
         'country',
         'phone_number',
         'notes',
     ]
Пример #15
0
    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)
Пример #16
0
 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
Пример #17
0
    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)
Пример #18
0
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
Пример #19
0
    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()
Пример #20
0
# -*- 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())
Пример #21
0
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'))
Пример #22
0
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)
Пример #23
0
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:
Пример #24
0
 class Meta:
     model = get_model('payment', 'Source')
Пример #25
0
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',
Пример #26
0
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)
Пример #27
0
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()
Пример #28
0
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:
Пример #29
0
 class Meta:
     model = get_model('payment', 'Transaction')
Пример #30
0
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