diff --git a/batiskaf/settings.py b/batiskaf/settings.py
index 753c246..b824e25 100644
--- a/batiskaf/settings.py
+++ b/batiskaf/settings.py
@@ -77,6 +77,7 @@ INSTALLED_APPS = (
'store',
'news',
'promo',
+ 'dtys',
)
SITE_ID = 1
diff --git a/batiskaf/templates/jinja2/dtys/index.jinja b/batiskaf/templates/jinja2/dtys/index.jinja
new file mode 100644
index 0000000..e8c18b2
--- /dev/null
+++ b/batiskaf/templates/jinja2/dtys/index.jinja
@@ -0,0 +1,43 @@
+{% extends 'base.jinja' %}
+
+{% block meta_description %}{% endblock %}
+
+{% block title %}
+ DTYS index
+{% endblock %}
+
+{% block stylesheet %}
+
+{% endblock stylesheet %}
+
+
+{% block jss %}
+
+{% endblock jss %}
+
+
+{% block content %}
+
+
+
D`TYS
+
+
+
+ {% for object in object_list %}
+ {% include "includes/dtys_product_thumb.jinja" with context %}
+ {% endfor %}
+
+
+
+
+
+{% endblock content %}
diff --git a/batiskaf/templates/jinja2/includes/dtys_product_thumb.jinja b/batiskaf/templates/jinja2/includes/dtys_product_thumb.jinja
new file mode 100644
index 0000000..aef87d8
--- /dev/null
+++ b/batiskaf/templates/jinja2/includes/dtys_product_thumb.jinja
@@ -0,0 +1,37 @@
+
+
+ {% set product = object.product.product %}
+ {% set im = product.main_image()|thumbnail("420x420") %}
+
+
+  }})
+
+
+
+ {% if not object.is_stopped %}
+ {{ object.current_price_format() }}
+ {% else %}
+ {{ object.price_stopped }}
+ {% endif %}
+
+
+
+
+
diff --git a/dtys/__init__.py b/dtys/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/dtys/admin.py b/dtys/admin.py
new file mode 100644
index 0000000..2cb272b
--- /dev/null
+++ b/dtys/admin.py
@@ -0,0 +1,15 @@
+from django.contrib import admin
+
+from .models import DTYSModel
+
+
+@admin.register(DTYSModel)
+class DTYSModelAdmin(admin.ModelAdmin):
+ list_display = (
+ 'id',
+ 'product',
+ 'is_stopped',
+ 'is_public'
+ )
+
+ prepopulated_fields = {"price_stopped": ("price_end",)}
diff --git a/dtys/apps.py b/dtys/apps.py
new file mode 100644
index 0000000..bd8236f
--- /dev/null
+++ b/dtys/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class DtysConfig(AppConfig):
+ "Drop `Til You Shop"
+ name = 'dtys'
diff --git a/dtys/migrations/0001_initial.py b/dtys/migrations/0001_initial.py
new file mode 100644
index 0000000..b929fba
--- /dev/null
+++ b/dtys/migrations/0001_initial.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.3 on 2017-10-26 22:45
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('store', '0061_auto_20170603_0220'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='DTYSModel',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('price_start', models.IntegerField()),
+ ('price_end', models.IntegerField()),
+ ('price_stopped', models.IntegerField(blank=True, null=True)),
+ ('start_date', models.DateField()),
+ ('start_time', models.TimeField()),
+ ('end_date', models.DateField()),
+ ('end_time', models.TimeField()),
+ ('is_stopped', models.BooleanField(default=False)),
+ ('is_public', models.BooleanField(default=False)),
+ ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='store.ProductVariation')),
+ ],
+ ),
+ ]
diff --git a/dtys/migrations/__init__.py b/dtys/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/dtys/models.py b/dtys/models.py
new file mode 100644
index 0000000..a4f9eb1
--- /dev/null
+++ b/dtys/models.py
@@ -0,0 +1,76 @@
+from datetime import datetime
+
+from django.db import models
+from store.models import ProductVariation
+
+
+def get_change(current, previous):
+ if current == previous:
+ return 100.0
+ try:
+ return (abs(current - previous) / previous) * 100.0
+ except ZeroDivisionError:
+ return 0
+
+
+class DTYSModel(models.Model):
+ product = models.ForeignKey(ProductVariation)
+
+ price_start = models.IntegerField()
+ price_end = models.IntegerField()
+ price_stopped = models.IntegerField(default=0)
+
+ start_date = models.DateField()
+ start_time = models.TimeField()
+
+ end_date = models.DateField()
+ end_time = models.TimeField()
+
+ is_stopped = models.BooleanField(default=False)
+ is_public = models.BooleanField(default=False)
+
+ @property
+ def end_datetime(self):
+ return datetime.combine(self.end_date, self.end_time)
+
+ @property
+ def start_datetime(self):
+ return datetime.combine(self.start_date, self.start_time)
+
+ def get_price_diff(self):
+ return self.price_start - self.price_end
+
+ def _time_percent(self):
+ time_percent = get_change(
+ self.get_delta_now(),
+ self.get_date_delta()
+ )
+ return time_percent
+
+ def get_delta_now(self):
+ """in seconds"""
+ from django.utils import timezone as tz
+ _now = tz.now()
+ return (self.end_datetime - _now).total_seconds()
+
+ def get_date_delta(self):
+ """ in seconds """
+ return (self.end_datetime - self.start_datetime).total_seconds()
+
+ def get_current_price(self):
+ dec_from_price = (self.get_price_diff() * (self._time_percent() / 100))
+ price_current = self.price_start - dec_from_price
+
+ return price_current
+
+ def current_price_format(self):
+ return "%.1f" % self.get_current_price()
+
+ def stop(self):
+ """Stop this DTYS"""
+ self.is_stopped = True
+
+ if not self.price_stopped:
+ self.price_stopped = max(self.price_end, self.get_current_price())
+
+ self.save()
diff --git a/dtys/tests.py b/dtys/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/dtys/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/dtys/urls.py b/dtys/urls.py
new file mode 100644
index 0000000..653b438
--- /dev/null
+++ b/dtys/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls import url
+
+from .views import DTYSListView, DTYSInfoView, DTYSBuyView, dtys_modified
+
+
+urlpatterns = [
+ url(r'^$', DTYSListView.as_view()),
+ url(u'^etg/$', dtys_modified),
+ url(u'^info/$', DTYSInfoView.as_view()),
+ url(u'^buy/(?P\d)/$', DTYSBuyView.as_view())
+]
diff --git a/dtys/views.py b/dtys/views.py
new file mode 100644
index 0000000..f41e300
--- /dev/null
+++ b/dtys/views.py
@@ -0,0 +1,114 @@
+from django.core.urlresolvers import reverse_lazy, reverse
+from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
+from django.utils import timezone
+from django.views.decorators.http import etag
+from django.views.generic import View, ListView, DetailView
+
+from .models import DTYSModel
+
+import logging
+
+log = logging.getLogger(__name__)
+
+
+def get_change(current, previous):
+ if current == previous:
+ return 100.0
+ try:
+ return (abs(current - previous) / previous) * 100.0
+ except ZeroDivisionError:
+ return 0
+
+
+class DTYSListView(ListView):
+ model = DTYSModel
+ template_name = "dtys/index.jinja"
+
+ def get_queryset(self, *args, **kwargs):
+ qs = super(DTYSListView, self).get_queryset(*args, **kwargs)
+ qs = qs.filter(
+ is_public=True,
+ start_date__lte=timezone.now().date(),
+ end_date__gte=timezone.now().date(),
+ start_time__lte=timezone.now().time(),
+ end_time__gte=timezone.now().time()
+ )
+ return qs
+
+
+class DTYSBuyView(DetailView):
+ model = DTYSModel
+
+ def get(self, request, *args, **kwargs):
+ obj = self.get_object()
+
+ log.debug(obj.id)
+
+ if obj.is_public and not obj.is_stopped:
+ from store.cart import Cart, CartItem
+ from store.models import ProductVariation
+
+ # stop DTYS for this product
+ obj.stop()
+
+ # Add to cart
+ cart = Cart(self.request)
+ try:
+ item_variant = ProductVariation.objects.get(pk=obj.product.pk)
+ except ProductVariation.DoesNotExist:
+ return HttpResponseRedirect('/')
+
+ cart.add_item(CartItem(item_variant, 1))
+
+ return HttpResponseRedirect('/store/cart/')
+
+ return HttpResponseRedirect('/dtys/')
+
+
+def latest_entry(request):
+ d = DTYSModel.objects.filter(
+ is_public=True,
+ is_stopped=False
+ )
+
+ if d.count() > 0:
+ pass
+
+ return "myETAG" + str(timezone.now().second)
+
+
+@etag(latest_entry)
+def dtys_modified(request):
+ return HttpResponse('')
+
+
+class DTYSInfoView(View):
+ """Ajax info"""
+
+ def get(self, request):
+ data = {
+ "dtys_list": []
+ }
+
+ dtys_list = DTYSModel.objects.filter(
+ is_public=True,
+ start_date__lte=timezone.now(),
+ end_date__gte=timezone.now(),
+ start_time__lte=timezone.now().time()
+ )
+
+ for i in dtys_list:
+ if i.end_time <= timezone.now().time():
+ i.stop()
+
+ dtys_item = {
+ 'id': i.id,
+ 'stopped': i.is_stopped
+ }
+
+ if not i.is_stopped:
+ dtys_item['current_price'] = i.current_price_format()
+
+ data['dtys_list'].append(dtys_item)
+
+ return JsonResponse(data)
diff --git a/static/js/dtys.js b/static/js/dtys.js
new file mode 100644
index 0000000..cbf348e
--- /dev/null
+++ b/static/js/dtys.js
@@ -0,0 +1,124 @@
+function getCookie(name) {
+ var cookieValue = null;
+ if (document.cookie && document.cookie !== '') {
+ var cookies = document.cookie.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ var cookie = cookies[i];
+ // Does this cookie string begin with the name we want?
+ if (cookie.substring(0, name.length + 1) === (name + '=')) {
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+ break;
+ }
+ }
+ }
+ return cookieValue;
+}
+
+function get_price() {
+ $.ajax({
+ url: "/dtys/info/",
+ method: "GET",
+ // beforeSend: function(xhr) {
+ // // var csrftoken = getCookie("csrftoken");
+
+ // // if (csrftoken) {
+ // // xhr.setRequestHeader("X-CSRFToken", csrftoken);
+ // // }
+ // },
+ success: function(data, textStatus, jqXHR) {
+ // console.log("DTYS data", data);
+ if (data.dtys_list.length > 0) {
+ var active = data.dtys_list;
+
+ active.forEach(function(i, idx, list) {
+ var product = $("#dtys_product_" + i.id);
+ var add_to_cart_btn = product.find(".add-to-cart");
+ var fake_add_to_cart_btn = product.find(".fake-add-to-cart");
+
+ if (i.stopped) {
+ product.addClass("stopped");
+ fake_add_to_cart_btn.show();
+ add_to_cart_btn.hide();
+ } else {
+ product.removeClass("stopped");
+ product.find(".price").text(i.current_price);
+ fake_add_to_cart_btn.hide();
+ add_to_cart_btn.show();
+ }
+ });
+ }
+ },
+ error: function(jqXHR, textStatus, errorThrow) {
+ console.log("что-то пошло не так, сервер не отвечает");
+ }
+ });
+};
+
+var Updater = function(){
+ this.params = {
+ period: 3000,
+ url: '',
+ onModified: function(data,x,modified){},
+ bgPause: false
+ };
+
+ this.interval = null;
+ this.ETag = '';
+ this.lastModified = '';
+
+ this.init = function(params){
+ var me = this;
+ this.params = $.extend(this.params, params);
+
+ if(this.params.bgPause){
+ $(window).blur(function(){ me.pause() });
+ $(window).focus(function(){ me.resume() });
+ }
+ };
+
+ this.start = function(){
+ var me = this;
+ this.interval = setInterval(function(){ me.doUpdate() }, this.params.period);
+ };
+
+ this.doUpdate = function(){
+ var me = this;
+ $.ajax(this.params.url, {
+ success: function(data,status,x){
+ if(me.ETag != x.getResponseHeader('ETag')){
+ me.params.onModified(data,x,me.lastModified);
+ me.lastModified = x.getResponseHeader('Last-Modified');
+ }
+ me.ETag = x.getResponseHeader('ETag');
+ },
+ beforeSend: function(x){
+ if(me.ETag != '') { x.setRequestHeader('If-None-Match', me.ETag); }
+ },
+ cache: false
+ });
+ };
+
+ this.pause = function(){
+ clearInterval(this.interval);
+ this.interval = null;
+ };
+
+ this.resume = function(){
+ if(this.interval != null) return;
+ this.start();
+ };
+};
+
+$(function(){
+ var upd = new Updater();
+
+ upd.init({
+ url: "/dtys/etg/",
+ onModified: function(data, x, modified) {
+ get_price();
+ console.log(data, x, modified);
+ }
+ });
+
+ upd.start();
+});