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