def order_refunded(self, order): """ The seller has refunded an order """ context = self._order_context(order) self._sendmail('order-refunded-to-customer', order.user.email, context) self._sendmail('order-refunded-to-stall-owner', order.stall.user.email, context) try: # Track the Seller refunding the Order order_info = self._mixpanel_order_info(order) mixpanel_track(self.request, "Clicked Refund Button", order_info) mixpanel_engage(self.request, {'$add': {'Refunds Made': 1}}) # Update the Buyers properties, reducing their GMV # XXX: we can't delete the transaction after it's refunded!!! # adding the transaction needs to happen on Dispatch user = order.user stall = order.stall mixpanel_engage(None, { '$set': { 'Orders': user.orders.completed().count(), 'Total GMV to Date': str( user.get_profile().total_gmv.amount), }, '$ignore_time': True }, distinct_id=user.id) except: LOG.warning("Couldn't notify MixPanel of refund", exc_info=True)
def user_followed(self, follow): """ :param follow: UserFollow object which has just been created """ mixpanel_track(self.request, "Followed User", {"followed_user": follow.target.username}) mixpanel_engage(self.request, get_mixpanel_info(follow.user), follow.user.id) mixpanel_engage(self.request, get_mixpanel_info(follow.target, ignore_time=True), follow.target.id) template_name = 'new-follower-notification' context = { 'USER_USERNAME': follow.user.username, 'USER_PROFILE_URL': absolute_uri(follow.user.get_profile().get_absolute_url()), 'TARGET_USER_USERNAME': follow.target.username, 'TARGET_USER_PROFILE_URL': absolute_uri(follow.target.get_profile().get_absolute_url()) } print absolute_uri(follow.user.get_profile().get_absolute_url()), \ absolute_uri(follow.target.get_profile().get_absolute_url()) email_notification = follow.target.email_notification if email_notification.follower_notifications: self._sendmail(template_name, follow.target.email, context) else: LOG.info( "Email not send to %s, since follower_notifications is set to OFF", follow.target.email)
def order_placed(self, order): """ 1) Syncs cart with SailThru 2) Sends 'Order Completed' e-mail to customer via Sailthru 3) Syncs order information with MixPanel """ # Technically the transaction isn't complete yet because the seller hasn't # marked the order as complete so could potentially refund the money. try: context = self._order_context(order) self._sendmail('order-placed-to-customer', order.user.email, context) self._sendmail('order-placed-to-stall-owner', order.stall.user.email, context) except: LOG.error("Events.order_placed failed to send email", exc_info=True) # Send completed order to SailThru, this updates the users 'Total Revenue' try: self.sailthru.order_purchased(order) except: LOG.error("Events.order_placed failed to sync order with SailThru", exc_info=True) # Sync user properties with MixPanel try: user = order.user mixpanel_engage(self.request, { '$set': { 'Orders': user.orders.completed().count(), 'Total GMV to Date': str( user.get_profile().total_gmv.amount), } }, distinct_id=user.id) mixpanel_engage(self.request, { '$append': { '$transactions': { '$time': order.created.isoformat(), '$amount': str(order.total().amount), 'Order ID': order.id } } }, distinct_id=user.id) if self.request is not None: order_info = self._mixpanel_order_info(order) mixpanel_track(self.request, "Purchased Order", order_info) except: LOG.warning("Couldn't notify MixPanel of new order", exc_info=True)
def user_unfollowed(self, unfollow): """ :param unfollow: UserFollow object which is about to be deleted """ mixpanel_track(self.request, "Unfollowed User", {"followed_user": unfollow.target.username}) mixpanel_engage(self.request, get_mixpanel_info(unfollow.user), unfollow.user.id) mixpanel_engage(self.request, get_mixpanel_info(unfollow.target, ignore_time=True), unfollow.target.id)
def checkout_add(request, slug): """ Adds product to cart. """ product = get_object_or_404(Product, slug=slug) country = None if request.POST.get('shipping_country', None): try: country = Country.objects.get( code=request.POST.get('shipping_country', None)) except: pass # When no cart is found, send them to login - where all users have carts if getattr(request.user, 'cart', None) is None: # This allows a streamlined log in process, the product_to_add_id # session variable is used by the register view to add a product # to the cart and redirect straight to the cart view. request.session["product_to_add_id"] = product.id login_url = reverse('login') return redirect_to_login(reverse('checkout_cart'), login_url=login_url) cart = request.user.cart try: # If the user is anonymous the cart won't exist in the DB yet # This avoids doing a DB insert for every request. if cart.id is None: cart.save() cart.add(product, country=country) mixpanel_track( request, 'Clicked Add to Cart', { 'Product Title': product.title, 'Product ID': product.id, 'Number in stock': 'unlimited' if product.stock is None else product.stock, 'Number of Images': product.images.all().count(), 'Has Welcome Video': product.stall.has_welcome_video(), 'Ships Worldwide': product.shipping_profile.ships_worldwide(), 'Price': str(product.get_price_instance().amount) }) Events(request).cart_updated(cart) except purchase.models.OutOfStockError: messages.error(request, "That product is out of stock") return HttpResponseRedirect(reverse('checkout_cart'))
def logged_in(self, user): """ The user has logged in to the site :param user: User object """ # Integration with sailthru if self.sailthru.enabled(): self.sailthru.login(user) # Internal LifetimeTrack try: lt = LifetimeTrack.objects.get(user=user) merge_lifetime_track(self.request.lifetime_track, lt) except Exception as e: print "=-" * 34, e # Integration with mixpanel try: mixpanel_track(self.request, "logged in", {"$last_seen": datetime.datetime.now()}) mixpanel_engage(self.request, {'$add': {'Times Logged In': 1}}) try: has_stall = user.stall is not None except: has_stall = False if has_stall: member_type = 'Regular' else: member_type = 'Stall Owner' mixpanel_engage( self.request, { '$set': { 'last_login': datetime.datetime.now().isoformat(), '$email': user.email, '$username': user.username, '$created': user.date_joined.isoformat(), "$last_seen": datetime.datetime.now().isoformat(), '$first_name': user.first_name, '$last_name': user.last_name, 'gender': user.user_profile.get_gender_display(), 'mp_name_tag': user.get_full_name(), 'Member Type': member_type, } }) except: LOG.warn('Could not send login event to MixPanel', exc_info=True)
def _confirm_stock_check(self): """ Updates the stalls last stock check datetime. :return: """ now = datetime.now(tz=pytz.utc) self.stall.last_stock_checked_at = now # If the stall is at a very frequent stock check level, # then move them to a 30 day level after a successful # stock check is complete. if self.stall.renewal_tier < 3: self.stall.renewal_tier = 3 if self.stall.is_suspended: self.stall.products.filter( status=Product.PUBLISHED_SUSPENDED ).update( status=Product.PUBLISHED_LIVE, updated=now ) self.stall.is_suspended = False status = StallStatusLog() status.stall = self.stall status.renewal_tier = self.stall.renewal_tier status.is_suspended = False status.reason_for_suspension = None status.updated_at = now status.save() self.stall.save() mixpanel_track( self.request, 'Renewed Products', ) messages.success(self.request, 'Thanks! Your stock check is done. ' 'You will need to do another stock check within the next %d days. ' 'We do however suggest that if your inventory changes frequently ' 'that you log in to Eco Market and and do these stock checks more often. ' 'You can do a stock check as many times as you like at any time, ' 'but at the moment you as required to do one every 30 days to keep ' 'your products live in our marketplace.' % _get_days_to_next_stockcheck(self.request))
def get(self, request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseRedirect( reverse('stockcheck_landing') ) # Handle stall owners who have no products at all # https://app.asana.com/0/8319277024263/8336587616129 if not request.user.stall.products.count(): messages.info(request, 'It seems like you are trying to do a stock ' 'check but you have no products listed. You are not required to do ' 'a stock check at the moment.') return HttpResponseRedirect( reverse('selling') ) # Handle stall owners with just unpublished products # https://app.asana.com/0/8319277024263/8336587616134 if (not request.user.stall.products.filter(status=Product.PUBLISHED_LIVE).count() and not request.user.stall.products.filter(status=Product.PUBLISHED_SUSPENDED).count()): messages.info(request, 'It seems like you are trying to do a stock ' 'check but you have no live or out of stock products. You cannot ' 'do a stock check on unpublished products (since they are ' 'completely removed from Eco Market) so first you must republish ' 'them, then you may do a stock check on these items. You can ' '<a href="%(url)s" target="_blank">read more</a> about the difference between ' 'unpublishing items and editing stock.' % dict( url='http://help.ecomarket.com/customer/portal/articles/1346382' )) return HttpResponseRedirect( reverse('selling') ) mixpanel_track( request, 'Viewed Stockcheck Page' ) messages.success(self.request, 'Thanks for helping us keep your items up to date on Eco Market. ' 'To find out more on what this stock check is and a video tutorial explaining how ' 'to use them you can see this <a href="%(url)s" target="_blank">help centre article</a>.' % dict( url='http://help.ecomarket.com/customer/portal/articles/1342284' )) return super(StockcheckUpdateView, self).get(request, *args, **kwargs)
def stockcheck_start(request, template_name='accounts/stockcheck/landing.html'): """ Allow a user to log in with either username/email and password. """ next_url = reverse('stockcheck_update') context = { 'next': next_url, 'social_login_error': int(request.GET.get('social-login-error', 0)), } mixpanel_track( request, 'Clicked CTA In Renewal Email' ) data = request.POST or None login_form = LoginForm(data=data) if request.user.is_authenticated(): return HttpResponseRedirect(next_url) if login_form.is_valid(): username = login_form.cleaned_data.get('username', None) password = login_form.cleaned_data.get('password', None) user = authenticate(username=username, password=password) login_success = user and not user.is_anonymous() if login_success: login(request, user) Events(request).logged_in(user) if not request.POST.get('remember_me', None): expiry = 0 else: expiry = SESSION_EXPIRY request.session.set_expiry(expiry) return HttpResponseRedirect(next_url) context.update({'login_form': login_form}) return render(request, template_name, context)
def message_sent(self, msg): """ A new message has been sent, notify the recipient via e-mail :param msg: Message object """ if not msg.parent_msg: template_name = "new-message-received" else: template_name = "message-reply-received" # TODO: notification settings context = { "SENDER_USERNAME": msg.sender.username, "MESSAGE_SUBJECT": msg.subject, "MESSAGE_BODY": msg.body, 'MESSAGE_VIEW_URL': absolute_uri(reverse('messaging_inbox')) } # send mail to hipchat template_context = Context({ 'request': self.request, 'message': msg, }) template = loader.get_template( "messaging/fragments/hipchat_message.html") output = template.render(template_context) send_to_hipchat( output, room_id=settings.HIPCHAT_MAIL_ROOM, notify=1, ) mixpanel_track(self.request, "Sent Message", {}) mixpanel_engage(self.request, {'$add': {'Messages Sent': 1}}) self._sendmail(template_name, msg.recipient.email, context)
def order_dispatched(self, order): """ The seller has marked an order as dispatched """ try: context = self._order_context(order) self._sendmail('order-dispatched-to-customer', order.user.email, context) except: LOG.warning("Couldn't send Order Dispatched email to Customer", exc_info=True) try: order_info = self._mixpanel_order_info(order) mixpanel_track(self.request, 'Clicked Mark Order as Dispatched', order_info) mixpanel_engage(self.request, {'$add': { 'Orders Dispatched': 1 }}, distinct_id=order.stall.user.id) except: LOG.warning("Couldn't update MixPanel after Order Dispatch", exc_info=True)
def checkout_shipping(request, cart_stall_id): """Asks the user to select a shipping address or create one""" if not isinstance(cart_stall_id, CartStall): cart_stall = get_object_or_404(CartStall, id=cart_stall_id) else: cart_stall = cart_stall_id cart_stall_id = cart_stall.id address_form = None address_with_error = None if request.method == "POST": address = None if "shipping_address_id" in request.POST: address_id = request.POST["shipping_address_id"] address = ShippingAddress.objects.get(id=address_id) else: # We have a new shipping address to validate and create share_orders = request.POST.get('check_order_in_activity_feed', False) request.session['check_orders_in_activity_feed'] = share_orders address_form = ShippingAddressForm(request.POST) if address_form.is_valid(): address = address_form.save(commit=False) address.user = request.user address.save() if address: address.last_select_date_time = timezone.now() address.save() try: cart_stall.set_address(address) cart_stall.save() return redirect( reverse('checkout_pay_stall', kwargs={"cart_stall_id": cart_stall.id})) except purchase.models.MismatchingCountryError: print("Mismatching country") messages.error( request, 'Oops. Your delivery prices have been worked out for %s ' 'only so the address you have selected isn\'t suitable. ' 'You can add/edit your addresses below or ' '<a href="%s" title="Back to your cart">go back to your cart</a> ' 'to edit your delivery prices for the country you would like to deliver to.' % (cart_stall.speculative_country.title, reverse('checkout_cart'))) address_with_error = address.id address_form = None # We save this address as is, even though it can't be shipped. except purchase.models.UnavailableShippingCountryError: messages.error( request, "Sorry that country isn't available for delivery") address_with_error = address.id address_form = None # We save this address as is, even though it can't be shipped. if address_form is None: address_form = ShippingAddressForm() share_orders = request.user.email_notification.share_orders_in_activity_feed request.session['check_orders_in_activity_feed'] = share_orders existing_addresses = request.user.addresses.all().order_by('-pk') context = { "cart_stall": cart_stall, "shipping_addresses": existing_addresses, "shipping_addresses_by_use": request.user.addresses.all().order_by('-last_select_date_time'), "address_form": address_form, 'share_orders': request.session['check_orders_in_activity_feed'], "address_with_error": address_with_error, } mixpanel_track(request, 'Clicked Continue to Payment at Delivery', {}) request.clicktale.record = True return render(request, "purchase/checkout_shipping.html", context)