3

\0                 @   s  d dl Z d dlZd dlZd dlZd dlmZ d dlmZ d dlm	Z	 d dl
mZ d dlmZ d dlmZ d dlmZ d d	lmZ d d
lmZ d dlmZmZ d dlmZ d dlmZ d dlm Z  d dl!m"Z"m#Z#m$Z$ d dl%m&Z& d dl'm(Z( e#dddg\Z)Z*e"ddZ+e#dddg\Z,Z-G dd dej.Z/G dd dej.Z0G dd de/ej.Z1G d d! d!e/ej.Z2G d"d# d#ej.Z3G d$d% d%ej.Z4G d&d' d'ej.Z5dS )(    N)Decimal)
ROUND_DOWN)settings)
exceptions)models)Q)date)reverse)cached_property)get_current_timezonenow)gettext_lazy)AUTH_USER_MODEL)
deprecated)	get_classget_classes	get_model)fields)currencyzoffer.managersActiveOfferManagerBrowsableRangeManagerzoffer.resultsZERO_DISCOUNTzoffer.utils
load_proxy
unit_pricec               @   sB   e Zd ZG dd dZdd Zdd Zedd Zed	d
 ZdS )BaseOfferMixinc               @   s   e Zd ZdZdS )zBaseOfferMixin.MetaTN)__name__
__module____qualname__abstract r   r   ;/var/www/html/oscar2019/oscar/apps/offer/abstract_models.pyMeta   s   r!   c             C   s   | j }| j|j kr| S t| j}x$t|j D ]}|jdr0||= q0W | jrpt	| j}| j|krf| S |f |S | j
|kr|| j
 f |S td| jjj | j
f dS )z(
        Return the proxy model
        _zUnrecognised %s type (%s)N)	proxy_map	__class__valuesdict__dict__listkeys
startswithproxy_classr   typeRuntimeErrorr   lower)selfZklassmap
field_dictfieldklassr   r   r    proxy"   s    






zBaseOfferMixin.proxyc             C   s   | j S )N)name)r/   r   r   r    __str__:   s    zBaseOfferMixin.__str__c             C   s(   | j  }| jr"| j|jkr"td|jS )z
        A plaintext description of the benefit/condition. Every proxy class
        has to implement it.

        This is used in the dropdowns within the offer dashboard.
        z,Name property is not defined on proxy class.)r3   r+   r$   AssertionErrorr4   )r/   Zproxy_instancer   r   r    r4   =   s    zBaseOfferMixin.namec             C   s   | j S )zi
        A description of the benefit/condition.
        Defaults to the name. May contain HTML.
        )r4   )r/   r   r   r    descriptionJ   s    zBaseOfferMixin.descriptionN)	r   r   r   r!   r3   r5   propertyr4   r7   r   r   r   r    r      s
   r   c                   s0  e Zd ZdZejedddeddZej	eddddd	Z
ejed
deddZdx\ZZZZeedfeedfeedfeedffZejedeeddZejededddZdy\ZZZejeddedZejd ejd!ed"d#Zejd$ejd!ed%d#Zejed&d'ed(d)Zejed*dded+d,Z ejed-dded.d,Z!ej"ed/ed0ddd1Z#ej"ed2ed3ddd1Z$ej"ed4dded5d,Z%ej&ed6d7d8dded9d:Z'ej&ed;d7d8e(d<d=Z)ej"ed>d'd?Z*ej"ed@d'd?Z+ej,edAddBZ-ejedCddDZ.ej/ Z0e1 Z2dEZ3G dFdG dGZ4 fdHdIZ5dJdK Z6dLdM Z7dNdO Z8e9dPdQ Z:e9dRdS Z;dTdU Z<de<_=dVdW Z>de>_=dzdXdYZ?dZd[ Z@d\d] ZAd^d_ ZBd`da ZCdbdc ZDddde ZEdfdg ZFd{dhdiZGdjdk ZHdldm ZIdndo ZJdeJ_=dpdq ZKdrds ZLe9dtdu ZMdvdw ZN  ZOS )|AbstractConditionalOfferz5
    A conditional offer (eg buy 1, get 10% off)
    Name   Tz.This is displayed within the customer's basket)
max_lengthunique	help_textSlugr4   )r<   r=   populate_fromDescriptionz,This is displayed on the offer browsing page)blankr>   SiteVoucherUserSessionz#Site offer - available to all userszJVoucher offer - only available after entering the appropriate voucher codez/User offer - available to certain types of userzWSession offer - temporary offer, available for a user for the duration of their sessionType)choicesdefaultr<   zExclusive offerz5Exclusive offers cannot be combined on the same items)r>   rI   Open	SuspendedConsumedStatus@   )r<   rI   zoffer.Conditionoffers	Condition)	on_deleterelated_nameverbose_namezoffer.BenefitBenefitZPriorityr   z-The highest priority offers are applied first)rI   r>   z
Start datezWOffers are active from the start date. Leave this empty if the offer has no start date.)rB   nullr>   zEnd datezWOffers are active until the end date. Leave this empty if the offer has no expiry date.zMax global applicationszCThe number of times this offer can be used before it is unavailable)r>   rB   rU   zMax user applicationsz4The number of times a single user can use this offerzMax basket applicationszEThe number of times this offer can be applied to a basket (and order)zMax discount      zgWhen an offer has given more discount to orders than this threshold, then the offer becomes unavailable)decimal_places
max_digitsrU   rB   r>   zTotal Discountz0.00)rX   rY   rI   zNumber of applications)rI   zNumber of OrderszURL redirect (optional))rB   zDate Created)auto_now_addNc               @   s,   e Zd ZdZdZddgZedZedZdS )zAbstractConditionalOffer.MetaTofferz	-prioritypkzConditional offerzConditional offersN)	r   r   r   r   	app_labelorderingr"   rS   verbose_name_pluralr   r   r   r    r!      s
   r!   c                s2   | j s$| j dkr| j| _n| j| _t j||S )Nr   )is_suspendedget_max_applicationsCONSUMEDstatusOPENsupersave)r/   argskwargs)r$   r   r    rf      s
    
zAbstractConditionalOffer.savec             C   s   t dd| jidS )Nzoffer:detailslug)rh   )r	   ri   )r/   r   r   r    get_absolute_url   s    z)AbstractConditionalOffer.get_absolute_urlc             C   s   | j S )N)r4   )r/   r   r   r    r5      s    z AbstractConditionalOffer.__str__c             C   s*   | j r&| jr&| j | jkr&tjtdd S )Nz(End date should be later than start date)start_datetimeend_datetimer   ValidationErrorr"   )r/   r   r   r    clean   s    zAbstractConditionalOffer.cleanc             C   s   | j | jkS )N)rc   rd   )r/   r   r   r    is_open   s    z AbstractConditionalOffer.is_openc             C   s   | j | jkS )N)rc   	SUSPENDED)r/   r   r   r    r`      s    z%AbstractConditionalOffer.is_suspendedc             C   s   | j | _| j  d S )N)rp   rc   rf   )r/   r   r   r    suspend   s    z AbstractConditionalOffer.suspendc             C   s   | j | _| j  d S )N)rd   rc   rf   )r/   r   r   r    	unsuspend  s    z"AbstractConditionalOffer.unsuspendc             C   sb   | j r
dS |dkrt }g }| jr2|j| j|k | jrH|j|| jk t|rTdS | j|dkS )zA
        Test whether this offer is available to be used
        FNr   )r`   r   rk   appendrl   anyra   )r/   userZ	test_dateZ
predicatesr   r   r    is_available  s    z%AbstractConditionalOffer.is_availablec             C   s   | j j j| |S )N)	conditionr3   is_satisfied)r/   basketr   r   r    is_condition_satisfied  s    z/AbstractConditionalOffer.is_condition_satisfiedc             C   s   | j j j| |S )N)rw   r3   is_partially_satisfied)r/   ry   r   r   r     is_condition_partially_satisfied  s    z9AbstractConditionalOffer.is_condition_partially_satisfiedc             C   s   | j j j| |S )N)rw   r3   get_upsell_message)r/   ry   r   r   r    r}     s    z+AbstractConditionalOffer.get_upsell_messagec             C   s(   | j |stS | jj j|| jj | S )zS
        Applies the benefit to the given basket and returns the discount.
        )rz   r   benefitr3   applyrw   )r/   ry   r   r   r    apply_benefit"  s    

z&AbstractConditionalOffer.apply_benefitc             C   s   | j j j|||S )z{
        Applies any deferred benefits.  These are things like adding loyalty
        points to someone's account.
        )r~   r3   apply_deferred)r/   ry   orderapplicationr   r   r    apply_deferred_benefit+  s    z/AbstractConditionalOffer.apply_deferred_benefitc             C   s
   || _ d S )N)_voucher)r/   voucherr   r   r    set_voucher2  s    z$AbstractConditionalOffer.set_voucherc             C   s   | j S )N)r   )r/   r   r   r    get_voucher5  s    z$AbstractConditionalOffer.get_voucherc             C   sz   | j r| j| j krdS dg}| jrB|rB|jtd| j| j|  | jrT|j| j | jrr|jtd| j| j  t	|S )zl
        Return the number of times this offer can be applied to a basket for a
        given user.
        r   i'  )
max_discounttotal_discountmax_user_applicationsrs   maxget_num_user_applicationsmax_basket_applicationsmax_global_applicationsnum_applicationsmin)r/   ru   Zlimitsr   r   r    ra   8  s    
z-AbstractConditionalOffer.get_max_applicationsc             C   sB   t dd}|jj| j|djtjdd}|d d k	r>|d S dS )Nr   OrderDiscount)offer_idZorder__user	frequency)totalr   r   )r   objectsfilterid	aggregater   Sum)r/   ru   r   
aggregatesr   r   r    r   M  s
    

z2AbstractConditionalOffer.get_num_user_applicationsc             C   s   | j j j|S )N)r~   r3   shipping_discount)r/   charger   r   r    r   T  s    z*AbstractConditionalOffer.shipping_discountc             C   s>   |  j |d 7  _ |  j|d 7  _|  jd7  _| j  d S )Nfreqdiscount   )r   r   
num_ordersrf   )r/   r   r   r   r    record_usageW  s    z%AbstractConditionalOffer.record_usagec             C   s    | j  }dd |D }dj|S )zF
        Return a description of when this offer is available
        c             S   s   g | ]}|d  qS )r7   r   ).0rr   r   r    
<listcomp>c  s    zEAbstractConditionalOffer.availability_description.<locals>.<listcomp>z<br/>)availability_restrictionsjoin)r/   restrictionsdescriptionsr   r   r    availability_description^  s    z1AbstractConditionalOffer.availability_descriptionc             C   s  g }| j r|jtddd | jrX| j| j }td| j|d }|j||dkd | jr| jdkrrtd}ntd	d
| ji }|j|dd | jr| jdkrtd}ntdd
| ji }|j|dd dd }| js| jrt	 }| jo| jr8td|| j|| jd }| j|  ko0| jkn  }nR| jrbtdd|| ji }|| jk}n(| jrtdd|| ji }|| jk}|j||d | j
rtddt| j
i }|j|| j| j
k d |S )NzOffer is suspendedF)r7   rx   z3Limited to %(total)d uses (%(remainder)d remaining))r   	remainderr   r   zLimited to 1 use per userz"Limited to %(total)d uses per userr   TzLimited to 1 use per basketz$Limited to %(total)d uses per basketc             S   sD   | j r| jt }n| }|jdkr8|jdkr8t|tjS t|tjS )Nr   )	tzinfo
astimezoner   hourminutedate_filterr   DATE_FORMATDATETIME_FORMAT)dt	localtimer   r   r    hide_time_if_zero  s    zMAbstractConditionalOffer.availability_restrictions.<locals>.hide_time_if_zeroz'Available between %(start)s and %(end)s)startendzAvailable from %(start)sr   zAvailable until %(end)sr   zLimited to a cost of %(max)sr   )r`   rs   r"   r   r   r   r   rk   rl   r   r   r   r   )r/   r   	remainingdescr   todayrx   r   r   r    r   f  sj    








 

z2AbstractConditionalOffer.availability_restrictionsc             C   s   | j jd k	S )N)rw   range)r/   r   r   r    has_products  s    z%AbstractConditionalOffer.has_productsc             C   s<   t dd}| js|jj S | jjj }|jddj|j	dS )z=
        Return a queryset of products in this offer
        	catalogueProductT)is_discountable)	structure)
r   r   r   nonerw   r   all_productsr   excludeCHILD)r/   r   querysetr   r   r    products  s    

z!AbstractConditionalOffer.products)rC   rD   rE   rF   )rJ   rK   rL   )NN)N)Pr   r   r   __doc__r   	CharFieldr"   r4   r   AutoSlugFieldri   	TextFieldr7   SITEZVOUCHERUSERZSESSIONTYPE_CHOICES
offer_typeBooleanField	exclusiverd   rp   rb   rc   
ForeignKeyCASCADErw   r~   IntegerFieldpriorityDateTimeFieldrk   rl   PositiveIntegerFieldr   r   r   DecimalFieldr   Dr   r   r   ExtendedURLFieldredirect_urldate_createdManagerr   r   activer   r!   rf   rj   r5   rn   r8   ro   r`   rq   alters_datarr   rv   rz   r|   r}   r   r   r   r   ra   r   r   r   r   r   r   r   __classcell__r   r   )r$   r    r9   S   s   











	
Ir9   c            	   @   sv  e Zd ZejdddejeddZdC\ZZ	Z
ZdD\ZZZeedfe	edfe
edfeedfeedfeedfeedffZejeddeddZejeddddddZejedddeddZejedddd ZG d!d" d"Zed#d$ Zd%d& Zd'd( Zd)d* Zd+d, Zd-d. Z d/d0 Z!d1d2 Z"d3d4 Z#d5d6 Z$d7d8 Z%d9d: Z&d;d< Z'd=d> Z(dEd?d@Z)dAdB Z*dS )FAbstractBenefitzoffer.RangeTRange)rB   rU   rQ   rS   
PercentageAbsoluteMultibuyFixed priceShipping percentageShipping absoluteShipping fixed pricez3Discount is a percentage off of the product's valuez5Discount is a fixed amount off of the product's valuez1Discount is to give the cheapest product for freez:Get the products that meet the condition for a fixed pricez/Discount is a fixed amount of the shipping costzGet shipping for a fixed pricez1Discount is a percentage off of the shipping costrG   r;   )r<   rH   rB   ValuerV   rW   )rX   rY   rU   rB   zMax Affected Itemsz]Set this to prevent the discount consuming all items within the range that are in the basket.)rB   rU   r>   zCustom class   N)r<   rI   c               @   s$   e Zd ZdZdZedZedZdS )zAbstractBenefit.MetaTr[   rT   ZBenefitsN)r   r   r   r   r]   r"   rS   r_   r   r   r   r    r!     s   r!   c             C   sX   | j tdd| jtdd| jtdd| jtdd| jtdd| jtdd| jtddiS )	Nzoffer.benefitsZPercentageDiscountBenefitZAbsoluteDiscountBenefitZMultibuyDiscountBenefitZFixedPriceBenefitZShippingAbsoluteDiscountBenefitZShippingFixedPriceBenefitZ!ShippingPercentageDiscountBenefit)
PERCENTAGEr   FIXEDMULTIBUYFIXED_PRICESHIPPING_ABSOLUTESHIPPING_FIXED_PRICESHIPPING_PERCENTAGE)r/   r   r   r    r#     s    zAbstractBenefit.proxy_mapc             C   s   t S )N)r   )r/   ry   rw   r[   r   r   r    r     s    zAbstractBenefit.applyc             C   s   d S )Nr   )r/   ry   r   r   r   r   r    r   
  s    zAbstractBenefit.apply_deferredc             C   s:   | j s
d S d| j j jdd }t| |r6t| |  d S )Nzclean_%s r"   )r,   r.   replacehasattrgetattr)r/   method_namer   r   r    rn     s
    
zAbstractBenefit.cleanc             C   sR   g }| j s|jtd | jr,|jtd | jr@|jtd |rNtj|d S )Nz)Multibuy benefits require a product rangez'Multibuy benefits don't require a valuez@Multibuy benefits don't require a 'max affected items' attribute)r   rs   r"   valuemax_affected_itemsr   rm   )r/   errorsr   r   r    clean_multibuy  s    zAbstractBenefit.clean_multibuyc             C   sX   g }| j s|jtd | js.|jtd n| jdkrF|jtd |rTtj|d S )Nz+Percentage benefits require a product rangez,Percentage discount benefits require a valued   z.Percentage discount cannot be greater than 100)r   rs   r"   r   r   rm   )r/   r   r   r   r    clean_percentage"  s    
z AbstractBenefit.clean_percentagec             C   sR   g }| j s|jtd | jr,|jtd | jr@|jtd |rNtj|d S )NzA discount value is requiredzFNo range should be selected as this benefit does not apply to productszAShipping discounts don't require a 'max affected items' attribute)r   rs   r"   r   r   r   rm   )r/   r   r   r   r    clean_shipping_absolute0  s    z'AbstractBenefit.clean_shipping_absolutec             C   sl   g }| j s|jtd n| j dkr2|jtd | jrF|jtd | jrZ|jtd |rhtj|d S )Nz,Percentage discount benefits require a valuer   z.Percentage discount cannot be greater than 100zFNo range should be selected as this benefit does not apply to productszAShipping discounts don't require a 'max affected items' attribute)r   rs   r"   r   r   r   rm   )r/   r   r   r   r    clean_shipping_percentage>  s    
z)AbstractBenefit.clean_shipping_percentagec             C   s>   g }| j r|jtd | jr,|jtd |r:tj|d S )NzFNo range should be selected as this benefit does not apply to productszAShipping discounts don't require a 'max affected items' attribute)r   rs   r"   r   r   rm   )r/   r   r   r   r    clean_shipping_fixed_priceO  s    z*AbstractBenefit.clean_shipping_fixed_pricec             C   s   | j rtjtdd S )NzHNo range should be selected as the condition range will be used instead.)r   r   rm   r"   )r/   r   r   r    clean_fixed_price[  s    z!AbstractBenefit.clean_fixed_pricec             C   s>   g }| j s|jtd | js,|jtd |r:tj|d S )Nz/Fixed discount benefits require a product rangez'Fixed discount benefits require a value)r   rs   r"   r   r   rm   )r/   r   r   r   r    clean_absolutea  s    zAbstractBenefit.clean_absolutec             C   s$   t tdrtj|S |jtdtS )z3
        Apply rounding to discount amount
        OSCAR_OFFER_ROUNDING_FUNCTIONz.01)r   r   r  quantizer   r   )r/   amountr   r   r    roundk  s    

zAbstractBenefit.roundc             C   s   | j r| j S dS )z
        Return the maximum number of items that can have a discount applied
        during the application of this benefit
        i'  )r   )r/   r   r   r    _effective_max_affected_itemss  s    z-AbstractBenefit._effective_max_affected_itemsc             C   s   |j o|jjS )zV
        Determines whether the benefit can be applied to a given basket line
        )stockrecordproductr   )r/   liner   r   r    can_apply_benefitz  s    z!AbstractBenefit.can_apply_benefitc             C   s   |dkr| j }g }x^|j D ]R}|j}|j| s| j| r@qt||}|sPq|j|dkr`q|j||f qW t|t	j
ddS )z
        Return the basket lines that are available to be discounted

        :basket: The basket
        :range: The range of products to use for filtering.  The fixed-price
                benefit ignores its range and uses the condition range
        Nr   )key)r   	all_linesr  contains_productr
  r   quantity_without_offer_discountrs   sortedoperator
itemgetter)r/   r[   ry   r   line_tuplesr	  r  pricer   r   r    get_applicable_lines  s    
z$AbstractBenefit.get_applicable_linesc             C   s   t dS )Nz0.00)r   )r/   r   r   r   r    r     s    z!AbstractBenefit.shipping_discount)r   r   r   r   )r   r   r   )N)+r   r   r   r   r   r   r"   r   r   r   r   r   r   r   r   r   r   r,   r   PositiveDecimalFieldr   r   r   NullCharFieldr+   r!   r8   r#   r   r   rn   r   r   r   r   r   r   r  r  r  r
  r  r   r   r   r   r    r     sV   







r   c               @   s   e Zd ZdZd&\ZZZeedfeedfeedffZe	j
dd	d	e	jed
dZe	jedded	dZejedddd	d	dZejeddddZG dd dZedd Zdd Zdd Zdd Zd d! Zd"d# Zd'd$d%ZdS )(AbstractConditionz
    A condition for an offer to be applied. You can either specify a custom
    proxy class, or need to specify a type, range and value.
    Countr   Coveragez@Depends on number of items in basket that are in condition rangez?Depends on value of items in basket that are in condition rangezHNeeds to contain a set number of DISTINCT items from the condition rangezoffer.RangeTr   )rB   rU   rQ   rS   rG   r;   )r<   rH   rB   rV   rW   )rX   rY   rU   rB   zCustom classr   N)r<   rI   c               @   s$   e Zd ZdZdZedZedZdS )zAbstractCondition.MetaTr[   rP   Z
ConditionsN)r   r   r   r   r]   r"   rS   r_   r   r   r   r    r!     s   r!   c             C   s(   | j tdd| jtdd| jtddiS )Nzoffer.conditionsZCountConditionZValueConditionZCoverageCondition)COUNTr   VALUECOVERAGE)r/   r   r   r    r#     s    zAbstractCondition.proxy_mapc             C   s   d S )Nr   )r/   r[   ry   Zaffected_linesr   r   r    consume_items  s    zAbstractCondition.consume_itemsc             C   s   dS )z
        Determines whether a given basket meets this condition.  This is
        stubbed in this top-class object.  The subclassing proxies are
        responsible for implementing it correctly.
        Fr   )r/   r[   ry   r   r   r    rx     s    zAbstractCondition.is_satisfiedc             C   s   dS )z
        Determine if the basket partially meets the condition.  This is useful
        for up-selling messages to entice customers to buy something more in
        order to qualify for an offer.
        Fr   )r/   r[   ry   r   r   r    r{     s    z(AbstractCondition.is_partially_satisfiedc             C   s   d S )Nr   )r/   r[   ry   r   r   r    r}     s    z$AbstractCondition.get_upsell_messagec             C   s$   |j s
dS |j}| jj|o"|j S )zX
        Determines whether the condition can be applied to a given basket line
        F)stockrecord_idr  r   r  get_is_discountable)r/   r	  r  r   r   r    can_apply_condition  s
    z%AbstractCondition.can_apply_conditionc             C   sh   g }x:|j  D ].}| j|sqt||}|s.q|j||f qW tjd}|r\t|d|dS t||dS )zW
        Return line data for the lines that can be consumed by this condition
        r   T)r	   r  )r  )r  r   r   rs   r  r  r  )r/   r[   ry   Zmost_expensive_firstr  r	  r  r  r   r   r    r    s    


z&AbstractCondition.get_applicable_lines)r  r   r  )T)r   r   r   r   r  r  r  r"   r   r   r   r   r   r   r,   r   r  r   r  r+   r!   r8   r#   r  rx   r{   r}   r   r  r   r   r   r    r    s2   



r  c               @   s  e Zd ZdZejeddddZej	edddddZ
ejdd	Zejed
deddZejedddZejdddedddZejdddeddZejdddeddZejdddeddZejeddddd Zejed!dd"ZdZdZdZdZdZej Ze  Z!G d#d$ d$Z"d%d& Z#d'd( Z$e%d)d* Z&dKd+d,Z'd-d. Z(d/d0 Z)e*d1d2 Z+d3d4 Z,e%d5d6 Z-d7d8 Z.d9d: Z/d;d< Z0d=d> Z1d?d@ Z2dAdB Z3dCdD Z4dEdF Z5e6dGdH Z7e6dIdJ Z8dS )LAbstractRangez
    Represents a range of products that can be used within an offer.

    Ranges only support adding parent or stand-alone products. Offers will
    consider child products automatically.
    r:   r;   T)r<   r=   r?   r4   )r<   r=   r@   )rB   z
Is public?Fz)Public ranges have a customer-facing page)rI   r>   zIncludes all products?)rI   zcatalogue.ProductZincludeszIncluded Productszoffer.RangeProduct)rR   rB   rS   throughZexcludeszExcluded Products)rR   rB   rS   zcatalogue.ProductClassclasseszProduct Typeszcatalogue.CategoryzIncluded CategorieszCustom classr   N)r<   rI   r=   zDate Created)rZ   c               @   s$   e Zd ZdZdZedZedZdS )zAbstractRange.MetaTr[   r   RangesN)r   r   r   r   r]   r"   rS   r_   r   r   r   r    r!   0  s   r!   c             C   s   | j S )N)r4   )r/   r   r   r    r5   6  s    zAbstractRange.__str__c             C   s   t dd| jidS )Nzcatalogue:rangeri   )rh   )r	   ri   )r/   r   r   r    rj   9  s    zAbstractRange.get_absolute_urlc             C   s   | j rt| j  S d S )N)r+   r   )r/   r   r   r    r3   =  s    zAbstractRange.proxyc             C   sr   |pd}t dd}|jj| |d|id\}}|dk	rL|j|krL||_|j  |j| j krn| jj| | j	  dS )a   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.
        r   r[   RangeProductdisplay_order)r   r  defaultsN)
r   r   get_or_creater&  rf   r   _excluded_product_idsexcluded_productsremoveinvalidate_cached_ids)r/   r  r&  Zinitial_orderr%  relation__r   r   r    add_productB  s    


zAbstractRange.add_productc             C   s6   t dd}|jj| |dj  | jj| | j  dS )z
        Remove product from range. To save on queries, this function does not
        check if the product is in fact in the range.
        r[   r%  )r   r  N)r   r   r   deleter*  addr,  )r/   r  r%  r   r   r    remove_product]  s    
zAbstractRange.remove_productc             C   s   | j r| j j|S | j }|j|kr(dS | jr2dS | j }|rP|j j|krPdS | j }|jrn|j	j|krndS |j|kr|dS | j
 }|rx<|j j| j D ](}x"|D ]}||ks|j|rdS qW qW dS )zI
        Check whether the passed product is part of this range.
        FT)r3   r  r)  r   includes_all_products
_class_idsget_product_class_included_product_idsis_childparent_included_categoriesget_categoriesonly_category_comparison_fieldsis_descendant_of)r/   r  Zexcluded_product_idsZ	class_idsZincluded_product_idsZtest_categoriescategoryZtest_categoryr   r   r    r  k  s0    




zAbstractRange.contains_productc             C   s
   | j |S )N)r  )r/   r  r   r   r    contains  s    zAbstractRange.containsc             C   s&   |j dd}tjj|}t|dh S )z
        Expects a product queryset; gets the primary keys of the passed
        products and their children.

        Verbose, but database and memory friendly.
        r\   Zchildren__pkN)values_list	itertoolschainfrom_iterableset)r/   r   Zpk_tuples_iterableZflat_iterabler   r   r    Z__get_pks_and_child_pks  s    z%AbstractRange.__get_pks_and_child_pksc             C   s   t dd}t|df S )Nr   CategoryCOMPARISON_FIELDS)r   r   )r/   rE  r   r   r    r<    s    
z)AbstractRange._category_comparison_fieldsc             C   s0   | j s| jj S | jd kr*| jj| j | _| jS )N)r   included_categoriesr   #_AbstractRange__included_categoriesr;  r<  )r/   r   r   r    r9    s    


z"AbstractRange._included_categoriesc             C   s(   | j s
g S | jd kr"| j| j| _| jS )N)r   $_AbstractRange__included_product_ids%_AbstractRange__get_pks_and_child_pksincluded_products)r/   r   r   r    r6    s    

z#AbstractRange._included_product_idsc             C   s(   | j s
g S | jd kr"| j| j| _| jS )N)r   $_AbstractRange__excluded_product_idsrJ  r*  )r/   r   r   r    r)    s    

z#AbstractRange._excluded_product_idsc             C   s"   | j d kr| jjddd| _ | j S )Nr\   T)flat)_AbstractRange__class_idsr#  r@  )r/   r   r   r    r4    s    
zAbstractRange._class_idsc             C   sX   | j d krRg }x<| j D ]0}|j jddd}|j|j |jt| qW || _ | j S )Nr\   T)rM  )_AbstractRange__category_idsr9  get_descendantsr@  rs   r\   extendr(   )r/   idsr>  Zchildren_idsr   r   r    _category_ids  s    

zAbstractRange._category_idsc             C   s   d | _ d | _d | _d | _d S )N)rO  rH  rI  rL  )r/   r   r   r    r,    s    z#AbstractRange.invalidate_cached_idsc             C   s&   | j r| j j S | jrd S | j j S )N)r3   num_productsr3  r   count)r/   r   r   r    rT    s
    
zAbstractRange.num_productsc             C   sx   | j r| j j S tdd}| jr6|jj j| j dS |jjt	| j
 dt	| j dB t	| j dB j| j dj S )z
        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.
        r   r   )Zid__in)Zproduct_class_id__in)Z productcategory__category_id__in)r3   r   r   r3  r   	browsabler   r)  r   r   r6  r4  rS  distinct)r/   r   r   r   r    r     s    


zAbstractRange.all_productsc             C   s   | j  S )zI
        Test whether this range can be edited in the dashboard.
        )r+   )r/   r   r   r    is_editable  s    zAbstractRange.is_editablec             C   s    t | j dkot | j dkS )zH
        Test whether products for the range can be re-ordered.
        r   )lenr4  r9  )r/   r   r   r    is_reorderable  s    zAbstractRange.is_reorderable)N)9r   r   r   r   r   r   r"   r4   r   r   ri   r   r7   r   Z	is_publicr3  ManyToManyFieldrK  r*  r#  rG  r  r+   r   r   rI  rL  rH  rN  rO  r   r   r   rV  r!   r5   rj   r
   r3   r/  r2  r  r   r?  rJ  r<  r9  r6  r)  r4  rS  r,  rT  r   r8   rX  rZ  r   r   r   r    r!    sh   
"	r!  c               @   sJ   e Zd ZdZejdejdZejdejdZej	ddZ
G dd dZd	S )
AbstractRangeProductzP
    Allow ordering products inside ranges
    Exists to allow customising.
    zoffer.Range)rQ   zcatalogue.Productr   )rI   c               @   s   e Zd ZdZdZdZdS )zAbstractRangeProduct.MetaTr[   r   r  N)r   r  )r   r   r   r   r]   unique_togetherr   r   r   r    r!     s   r!   N)r   r   r   r   r   r   r   r   r  r   r&  r!   r   r   r   r    r\    s
   r\  c               @   sJ  e Zd ZejdejdeddZejedddZ	ej
edZejeejed	d
ZejedddZd,\ZZZeefeefeeffZejeddeedZejeddddZejedddZej
edddZej
edddZej
edddZG dd dZedd Zd-d d!Zd"d# Zd$d% Zd&d' Z d(d) Z!d*d+ Z"dS ).AbstractRangeProductFileUploadzoffer.RangeZfile_uploadsr   )rQ   rR   rS   z	File Pathr   )r<   SizezUploaded By)rQ   rS   zDate UploadedT)rZ   PendingFailed	ProcessedrM       )r<   rH   rI   zError Message)r<   rB   zDate Processed)rU   zNumber of New SKUszNumber of Unknown SKUszNumber of Duplicate SKUsc               @   s(   e Zd ZdZdZdZedZedZdS )z#AbstractRangeProductFileUpload.MetaTr[   -date_uploadedzRange Product Uploaded FilezRange Product Uploaded FilesN)rd  )	r   r   r   r   r]   r^   r"   rS   r_   r   r   r   r    r!   7  s
   r!   c             C   s   t jj| jS )N)ospathbasenamefilepath)r/   r   r   r    filename>  s    z'AbstractRangeProductFileUpload.filenameNc             C   s"   t  | _|| _| j| _| j  d S )N)r   date_processederror_messageFAILEDrc   rf   )r/   messager   r   r    mark_as_failedB  s    z-AbstractRangeProductFileUpload.mark_as_failedc             C   s.   | j | _t | _|| _|| _|| _| j  d S )N)	PROCESSEDrc   r   rj  num_new_skusnum_unknown_skusnum_duplicate_skusrf   )r/   Znum_newZnum_unknownZnum_duplicater   r   r    mark_as_processedH  s    z0AbstractRangeProductFileUpload.mark_as_processedc             C   s   | j | jkS )N)rc   ro  )r/   r   r   r    was_processing_successfulP  s    z8AbstractRangeProductFileUpload.was_processing_successfulc             C   s  t | j }| jj }|jddd}t tt|}|jddd}t tt|}|j|}|| }tdd}|j	jt
j|dt
j|dB }x|D ]}| jj| qW |jddd}	t tt|	}	t tt|jddd}
|	j|
}|| }t |j|}| j|j t|t| |S )	zG
        Process the file upload and add products to the range
        Zstockrecords__partner_skuT)rM  upcr   r   )Zstockrecords__partner_sku__in)Zupc__in)rD  extract_idsr   r   r@  r   boolunionr   _default_managerr   r   r/  intersectionrs  rU  rY  )r/   Zall_idsr   Zexisting_skusZexisting_upcsZexisting_idsnew_idsr   r  Z
found_skusZ
found_upcsZ	found_idsZmissing_idsZdupesr   r   r    processS  s0    







z&AbstractRangeProductFileUpload.processc             c   sJ   t | jd4}x,|D ]$}xtjd|D ]}|r&|V  q&W qW W dQ R X dS )zD
        Extract all SKU- or UPC-like strings from the file
        r   z	[^\w:\.-]N)openrh  resplit)r/   fhr	  r   r   r   r    rv  t  s
    
z*AbstractRangeProductFileUpload.extract_idsc             C   s   t j| j d S )N)re  unlinkrh  )r/   r   r   r    delete_file~  s    z*AbstractRangeProductFileUpload.delete_file)r`  ra  rb  )N)#r   r   r   r   r   r   r"   r   r   rh  r   sizer   Zuploaded_byr   Zdate_uploadedPENDINGrl  ro  rH   rc   rk  rj  rp  rq  rr  r!   r8   ri  rn  rs  rt  r|  rv  r  r   r   r   r    r^    sF   




!
r^  )6rA  r  re  r~  decimalr   r   r   django.confr   django.corer   	django.dbr   django.db.models.queryr   django.template.defaultfiltersr   r   django.urlsr	   django.utils.functionalr
   Zdjango.utils.timezoner   r   django.utils.translationr   r"   oscar.core.compatr   oscar.core.decoratorsr   Zoscar.core.loadingr   r   r   Zoscar.modelsr   #oscar.templatetags.currency_filtersr   r   r   r   r   r   Modelr   r9   r   r  r!  r\  r^  r   r   r   r    <module>   sD   
5  o a_  	