parent
3f352e2cc8
commit
47019cf459
13 changed files with 464 additions and 0 deletions
@ -0,0 +1,43 @@ |
|||||||
|
{% extends 'base.jinja' %} |
||||||
|
|
||||||
|
{% block meta_description %}{% endblock %} |
||||||
|
|
||||||
|
{% block title %} |
||||||
|
DTYS index |
||||||
|
{% endblock %} |
||||||
|
|
||||||
|
{% block stylesheet %} |
||||||
|
<style> |
||||||
|
.dtys__product { |
||||||
|
margin-bottom: 20pt; |
||||||
|
border: 1px dashed lightgray; |
||||||
|
padding: 4pt; |
||||||
|
} |
||||||
|
.dtys .stopped { |
||||||
|
border: 2px solid red; |
||||||
|
} |
||||||
|
</style> |
||||||
|
{% endblock stylesheet %} |
||||||
|
|
||||||
|
|
||||||
|
{% block jss %} |
||||||
|
<script src="/static/js/dtys.js"></script> |
||||||
|
{% endblock jss %} |
||||||
|
|
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<div class="index-goods"> |
||||||
|
<div class="row"> |
||||||
|
<h1>D`TYS</h1> |
||||||
|
<input type="hidden" value="{{ csrf_token }}"> |
||||||
|
|
||||||
|
<div class="dtys"> |
||||||
|
{% for object in object_list %} |
||||||
|
{% include "includes/dtys_product_thumb.jinja" with context %} |
||||||
|
{% endfor %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
{% endblock content %} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
<div class="col-md-4 col-xs-4 col-sm-4 col-lg-4"> |
||||||
|
<div class="thumbnail {% if object.is_stopped %}stopped{% endif %}" id="dtys_product_{{ object.id }}"> |
||||||
|
{% set product = object.product.product %} |
||||||
|
{% set im = product.main_image()|thumbnail("420x420") %} |
||||||
|
|
||||||
|
<a href="{{ product.get_absolute_url() }}"> |
||||||
|
<img src="/static/{{ im.url|watermark('medium-photo') }}" |
||||||
|
class="img-responsive" alt="Купить {{ product.title }}" |
||||||
|
title="Купить {{ product.title }}" width="210" height="210"></a> |
||||||
|
<div class="caption"> |
||||||
|
<div class="title"> |
||||||
|
<a href="{{ product.get_absolute_url() }}"> |
||||||
|
{{ product.title }} ({{ object.product.product.id }}) |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
<div class="price"> |
||||||
|
{% if not object.is_stopped %} |
||||||
|
{{ object.current_price_format() }} |
||||||
|
{% else %} |
||||||
|
{{ object.price_stopped }} |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
<div class="in-cart"> |
||||||
|
<a class="btn btn-warning btn-block add-to-cart" |
||||||
|
href="/dtys/buy/{{ object.id }}/" |
||||||
|
{% if object.is_stopped %}style="display: none"{% endif %}> |
||||||
|
<span><i class="glyphicon glyphicon-shopping-cart" |
||||||
|
aria-hidden="true"></i> Добавить в корзину</span> |
||||||
|
</a> |
||||||
|
<span class="btn btn-warning btn-block fake-add-to-cart" {% if not object.is_stopped %}style="display: none"{% endif %}> |
||||||
|
<span><i class="glyphicon glyphicon-shopping-cart" |
||||||
|
aria-hidden="true"></i> Добавить в корзину</span> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
@ -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",)} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
from django.apps import AppConfig |
||||||
|
|
||||||
|
|
||||||
|
class DtysConfig(AppConfig): |
||||||
|
"Drop `Til You Shop" |
||||||
|
name = 'dtys' |
||||||
@ -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')), |
||||||
|
], |
||||||
|
), |
||||||
|
] |
||||||
@ -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() |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
from django.test import TestCase |
||||||
|
|
||||||
|
# Create your tests here. |
||||||
@ -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<pk>\d)/$', DTYSBuyView.as_view()) |
||||||
|
] |
||||||
@ -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) |
||||||
@ -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(); |
||||||
|
}); |
||||||
Loading…
Reference in new issue