From 47019cf45960ba4fa9efe5cae3d6ac3f804b33f4 Mon Sep 17 00:00:00 2001 From: Stepan Krapivin Date: Fri, 27 Oct 2017 00:31:03 +0500 Subject: [PATCH] add dtys app --- batiskaf/settings.py | 1 + batiskaf/templates/jinja2/dtys/index.jinja | 43 ++++++ .../jinja2/includes/dtys_product_thumb.jinja | 37 ++++++ dtys/__init__.py | 0 dtys/admin.py | 15 +++ dtys/apps.py | 6 + dtys/migrations/0001_initial.py | 34 +++++ dtys/migrations/__init__.py | 0 dtys/models.py | 76 +++++++++++ dtys/tests.py | 3 + dtys/urls.py | 11 ++ dtys/views.py | 114 ++++++++++++++++ static/js/dtys.js | 124 ++++++++++++++++++ 13 files changed, 464 insertions(+) create mode 100644 batiskaf/templates/jinja2/dtys/index.jinja create mode 100644 batiskaf/templates/jinja2/includes/dtys_product_thumb.jinja create mode 100644 dtys/__init__.py create mode 100644 dtys/admin.py create mode 100644 dtys/apps.py create mode 100644 dtys/migrations/0001_initial.py create mode 100644 dtys/migrations/__init__.py create mode 100644 dtys/models.py create mode 100644 dtys/tests.py create mode 100644 dtys/urls.py create mode 100644 dtys/views.py create mode 100644 static/js/dtys.js 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") %} + + + Купить {{ product.title }} +
+ +
+ {% 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(); +});