Compare commits

...

31 Commits

Author SHA1 Message Date
Stepan Krapivin 78e522b1fa update celery 8 years ago
Stepan Krapivin 16608ac882 improve dtys etag updates 8 years ago
Stepan Krapivin 1b00dd3df7 add PROD_MODE to jinja environment 8 years ago
Stepan Krapivin aece04aa6c increase styles version in base template 8 years ago
Stepan Krapivin a0f1e499af add sold-block to dtys thumbnail 8 years ago
Stepan Krapivin a690891eaf add batiskaf.context_processors.dev 8 years ago
Stepan Krapivin 9a9cbea429 add styles for dtys.wait 8 years ago
Stepan Krapivin ed9bd43fb0 disable has_discount when DTYSModel.save 8 years ago
Stepan Krapivin b283f5f758 add static/admin to gitignore 8 years ago
Stepan Krapivin 4e188bbfbf add store merged migrations from production 8 years ago
Stepan Krapivin 29a55180e3 add start_celery.sh 8 years ago
Stepan Krapivin c95c522754 add ProductVariation.has_discount boolean field 8 years ago
Stepan Krapivin 4ad070c9bc add verbose_name for dtys app and model 8 years ago
Stepan Krapivin a8957c9d10 add dtys.requirements 8 years ago
Stepan Krapivin 5a56c404a4 add is_ordered field to DTYSModel 8 years ago
Stepan Krapivin 1603c2bd50 add doUpdate to Updater.resume in dtys.js 8 years ago
Stepan Krapivin 4aa8633aae add dtys.tasks 8 years ago
Stepan Krapivin 0d8a608da0 fixes in thumbnails and watermarks 8 years ago
Stepan Krapivin d5e72786fe change title for dtys index page 8 years ago
Stepan Krapivin 7d372f1ad3 exclude stopped dtys in DTYSBuyView 8 years ago
Stepan Krapivin 99c2a596c0 fix dtys.urls 8 years ago
Stepan Krapivin f40786a535 fix dtys.urls 8 years ago
Stepan Krapivin 58d082e8d5 verbose names for DTYSModel fields 8 years ago
Stepan Krapivin 2ada05ea76 Merge branch 'master' of bitbucket.org:spacenergy/batiskaf 8 years ago
Stepan Krapivin ab8a623b74 update gitignore file 8 years ago
Stepan Krapivin b6a6fc47ea update dtys.views 8 years ago
Stepan Krapivin 0b37076dfd dtys app improve js counter 8 years ago
Stepan Krapivin 47019cf459 add dtys app 8 years ago
Stepan Krapivin 3f352e2cc8 fixes in urls 8 years ago
Dmitriy Shesterkin a976fe8405 emails list 8 years ago
Dmitriy Shesterkin ec5e86ce3f emails list 8 years ago
  1. 3
      .gitignore
  2. 7
      batiskaf/__init__.py
  3. 2
      batiskaf/celery.py
  4. 15
      batiskaf/context_processors.py
  5. 8
      batiskaf/jinja2.py
  6. 6
      batiskaf/jinja2_ext/thumbnails.py
  7. 2
      batiskaf/jinja2_ext/watermarks.py
  8. 20
      batiskaf/settings.py
  9. 7
      batiskaf/templates/jinja2/base.jinja
  10. 29
      batiskaf/templates/jinja2/dtys/index.jinja
  11. 50
      batiskaf/templates/jinja2/includes/dtys_product_thumb.jinja
  12. 131
      batiskaf/urls.py
  13. 10
      dtys/README.md
  14. 1
      dtys/__init__.py
  15. 14
      dtys/admin.py
  16. 8
      dtys/apps.py
  17. 34
      dtys/migrations/0001_initial.py
  18. 65
      dtys/migrations/0002_auto_20171103_2158.py
  19. 0
      dtys/migrations/__init__.py
  20. 95
      dtys/models.py
  21. 2
      dtys/requirements.txt
  22. 11
      dtys/tasks.py
  23. 3
      dtys/tests.py
  24. 12
      dtys/urls.py
  25. 165
      dtys/views.py
  26. 3
      requirements.txt
  27. 3
      start_celery.sh
  28. 162
      static/js/dtys.js
  29. 72
      static/less/_.less
  30. 2
      static/less/_.min.css
  31. 19
      store/migrations/0045_auto_20151223_1646.py
  32. 15
      store/migrations/0046_merge.py
  33. 15
      store/migrations/0047_merge.py
  34. 15
      store/migrations/0048_merge.py
  35. 15
      store/migrations/0049_merge.py
  36. 16
      store/migrations/0050_merge.py
  37. 16
      store/migrations/0051_merge.py
  38. 16
      store/migrations/0052_merge.py
  39. 16
      store/migrations/0054_merge.py
  40. 16
      store/migrations/0055_merge.py
  41. 16
      store/migrations/0058_merge.py
  42. 16
      store/migrations/0060_merge.py
  43. 25
      store/migrations/0061_auto_20170603_0227.py
  44. 16
      store/migrations/0062_merge.py
  45. 20
      store/migrations/0062_productvariation_has_discount.py
  46. 16
      store/migrations/0063_merge.py
  47. 31
      store/models.py

3
.gitignore vendored

@ -16,6 +16,7 @@ media/cache/*
static/photo_uploads/* static/photo_uploads/*
static/item_photos/* static/item_photos/*
static/adminfiles/* static/adminfiles/*
static/admin/*
static/cache/* static/cache/*
.vscode/* .vscode/*
/node_modules/ /node_modules/
@ -23,3 +24,5 @@ static/cache/*
!/dist !/dist
.sass-cache .sass-cache
local_* local_*
*.log
/static/dtys_update

@ -0,0 +1,7 @@
from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ['celery_app']

@ -9,7 +9,7 @@ from django.conf import settings
# set the default Django settings module for the 'celery' program. # set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'batiskaf.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'batiskaf.settings')
app = Celery('batiskaf') app = Celery('batiskaf', broker='redis://localhost:6379/0')
# Using a string here means the worker will not have to # Using a string here means the worker will not have to
# pickle the object when using Windows. # pickle the object when using Windows.

@ -0,0 +1,15 @@
from django.conf import settings
def dev(request):
try:
PROD_MODE = settings.PROD_MODE
except AttributeError:
PROD_MODE = True
context = {
'PROD_MODE': PROD_MODE
}
context['request'] = request
return context

@ -10,6 +10,13 @@ from jinja2 import Environment
from store.models import Category, Brand, Currency from store.models import Category, Brand, Currency
from django.conf import settings
try:
PROD_MODE = settings.PROD_MODE
except AttributeError:
PROD_MODE = True
def environment(**options): def environment(**options):
env = Environment(**options) env = Environment(**options)
env.globals.update({ env.globals.update({
@ -18,6 +25,7 @@ def environment(**options):
'main_categories': Category.objects.filter(parent__isnull=True).order_by('pk'), 'main_categories': Category.objects.filter(parent__isnull=True).order_by('pk'),
'all_brands': Brand.objects.order_by('title'), 'all_brands': Brand.objects.order_by('title'),
'currencies': Currency.objects.all(), 'currencies': Currency.objects.all(),
'PROD_MODE': PROD_MODE
}) })
env.filters['linebreaks'] = linebreaks env.filters['linebreaks'] = linebreaks
env.filters['thumbnail'] = thumbnail env.filters['thumbnail'] = thumbnail

@ -1,7 +1,11 @@
from easy_thumbnails.files import get_thumbnailer from easy_thumbnails.files import get_thumbnailer
from easy_thumbnails.exceptions import InvalidImageFormatError
def thumbnail(img, size='420x420'): def thumbnail(img, size='420x420'):
weight, height = size.split('x') weight, height = size.split('x')
options = {'size': (int(weight), int(height)), 'background': '#fff'} options = {'size': (int(weight), int(height)), 'background': '#fff'}
return get_thumbnailer(img).get_thumbnail(options) try:
return get_thumbnailer(img).get_thumbnail(options)
except (ValueError, InvalidImageFormatError):
return {'url': ''}

@ -3,4 +3,6 @@ from watermarker import core
def watermark(url, wm): def watermark(url, wm):
if url is None:
url = ''
return core.watermark(url, wm) return core.watermark(url, wm)

@ -16,13 +16,11 @@ import platform
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
ADMINS = ( ADMINS = (
('mitri4', 'mitri4@bk.ru'),
('Stepan', 'coder@xevin.ru') ('Stepan', 'coder@xevin.ru')
) )
MANAGERS = ( MANAGERS = (
('admin', 'admin@batiskaf-kz.kz'), ('admin', 'admin@batiskaf-kz.kz'),
('mitri4', 'mitri4@bk.ru'),
) )
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
@ -35,7 +33,12 @@ DEBUG = (platform.node() != 'Batiskaf')
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG
ALLOWED_HOSTS = ['batiskaf.kz', 'www.batiskaf.kz', 'batiskaf-kz.kz', 'www.batiskaf-kz.kz'] ALLOWED_HOSTS = [
'batiskaf.kz',
'www.batiskaf.kz',
'batiskaf-kz.kz',
'www.batiskaf-kz.kz'
]
MESSAGE_TAGS = { MESSAGE_TAGS = {
messages.INFO: 'info', messages.INFO: 'info',
@ -60,7 +63,7 @@ INSTALLED_APPS = (
'django.contrib.postgres', 'django.contrib.postgres',
'django.contrib.sitemaps', 'django.contrib.sitemaps',
'django.contrib.sites', 'django.contrib.sites',
#'django_extensions', # 'django_extensions',
'watermarker', 'watermarker',
'rest_framework', 'rest_framework',
'easy_thumbnails', 'easy_thumbnails',
@ -72,6 +75,7 @@ INSTALLED_APPS = (
'store', 'store',
'news', 'news',
'promo', 'promo',
'dtys',
) )
SITE_ID = 1 SITE_ID = 1
@ -153,7 +157,6 @@ STATICFILES_FINDERS = [
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), os.path.join(BASE_DIR, "static"),
] ]
TEMPLATES = [ TEMPLATES = [
@ -181,6 +184,7 @@ TEMPLATES = [
"django.core.context_processors.tz", "django.core.context_processors.tz",
"django.core.context_processors.request", "django.core.context_processors.request",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
# "batiskaf.context_processors.dev"
) )
}, },
}, },
@ -286,3 +290,9 @@ DPD_ID = '1122000140'
DPD_KEY = '0599EDC793B39FA6C098C3A8A7B5C603BBD3BE56' DPD_KEY = '0599EDC793B39FA6C098C3A8A7B5C603BBD3BE56'
DPD_DEV_SERVER = 'http://wstest.dpd.ru' DPD_DEV_SERVER = 'http://wstest.dpd.ru'
DPD_SERVER = 'http://ws.dpd.ru' DPD_SERVER = 'http://ws.dpd.ru'
RESUME_DTYS_DELAY_SECONDS = 60 * 3
PROD_MODE = True # set to False on test/dev server

@ -17,7 +17,7 @@
<link href='/static/favicon.ico?v=3' type='image/x-icon' rel='shortcut icon'/> <link href='/static/favicon.ico?v=3' type='image/x-icon' rel='shortcut icon'/>
<link href="/static/bower_components/Bootflat/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/bower_components/Bootflat/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/bower_components/Bootflat/bootflat/css/bootflat.min.css" rel="stylesheet"> <link href="/static/bower_components/Bootflat/bootflat/css/bootflat.min.css" rel="stylesheet">
<link href="/static/less/_.min.css?v=16" rel="stylesheet"> <link href="/static/less/_.min.css?v=17" rel="stylesheet">
<link href="/static/less/icons.css?v=1" rel="stylesheet"> <link href="/static/less/icons.css?v=1" rel="stylesheet">
<link href="/static/css/select2.min.css" rel="stylesheet"> <link href="/static/css/select2.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="/static/bower_components/sweetalert/lib/sweet-alert.css"> <link rel="stylesheet" type="text/css" href="/static/bower_components/sweetalert/lib/sweet-alert.css">
@ -247,6 +247,7 @@
</p> </p>
</div> </div>
<div class="col-xs-6 text-right"> <div class="col-xs-6 text-right">
{% if PROD_MODE %}
<!-- Yandex.Metrika informer --> <!-- Yandex.Metrika informer -->
<a href="https://metrika.yandex.ru/stat/?id=30685703&amp;from=informer" <a href="https://metrika.yandex.ru/stat/?id=30685703&amp;from=informer"
target="_blank" rel="nofollow"><img target="_blank" rel="nofollow"><img
@ -297,6 +298,7 @@
border=0 height=31 width=88 border=0 height=31 width=88
alt='Дайвинг - рейтинг DIVEtop'></a> alt='Дайвинг - рейтинг DIVEtop'></a>
<!--/COUNTER--> <!--/COUNTER-->
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -393,6 +395,8 @@
{% endfor %} {% endfor %}
</script> </script>
{% endif %} {% endif %}
{% if PROD_MODE %}
<!-- BEGIN JIVOSITE CODE {literal} --> <!-- BEGIN JIVOSITE CODE {literal} -->
<script type='text/javascript'> <script type='text/javascript'>
(function () { (function () {
@ -405,5 +409,6 @@
ss.parentNode.insertBefore(s, ss); ss.parentNode.insertBefore(s, ss);
})();</script> })();</script>
<!-- {/literal} END JIVOSITE CODE --> <!-- {/literal} END JIVOSITE CODE -->
{% endif %}
</body> </body>
</html> </html>

@ -0,0 +1,29 @@
{% extends 'base.jinja' %}
{% block meta_description %}{% endblock %}
{% block title %}
Поймай свою цену
{% endblock %}
{% block stylesheet %}{% endblock stylesheet %}
{% block jss %}
<script src="/static/js/dtys.js"></script>
{% endblock jss %}
{% block content %}
<div class="index-goods">
<div class="row">
<h1>Поймай свою цену</h1>
<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,50 @@
<div class="col-md-4 col-xs-4 col-sm-4 col-lg-4">
<div class="thumbnail dtys__product {% if object.is_stopped %}stopped{% endif %}" id="dtys_product_{{ object.id }}">
<div class="wait-block">
<span class="wait-icon glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>
</div>
<div class="sold-block alert-danger">
<div class="sold-block__message">Продано!</div>
</div>
{% set product = object.product.product %}
{% set im = product.main_image()|thumbnail("420x420") %}
<a>
<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">
<span>
{{ product.title }}
</span>
</div>
<div class="price counter">
{% 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>

@ -65,68 +65,69 @@ sitemaps1 = {
'categories': CategorySitemap, 'categories': CategorySitemap,
} }
urlpatterns = ( urlpatterns = [
url(r'^$', 'main.views.index', url(r'^$', 'main.views.index',
name='index'), name='index'),
url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps1}, url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps1},
name='django.contrib.sitemaps.views.sitemap'), name='django.contrib.sitemaps.views.sitemap'),
url(r'^robots\.txt$', lambda r: HttpResponse( url(r'^robots\.txt$', lambda r: HttpResponse(
"User-agent: *\nDisallow: \nHost: batiskaf-kz.kz\nSitemap: http://batiskaf-kz.kz/sitemap.xml", "User-agent: *\nDisallow: \nHost: batiskaf-kz.kz\nSitemap: http://batiskaf-kz.kz/sitemap.xml",
content_type="text/plain")), content_type="text/plain")),
url(r'^size/$', 'main.views.size_index', url(r'^size/$', 'main.views.size_index',
name='size_index'), name='size_index'),
url(r'^feedback/$', 'main.views.feedback', url(r'^feedback/$', 'main.views.feedback',
name='feedback'), name='feedback'),
url(r'^oferta/$', 'main.views.oferta', url(r'^oferta/$', 'main.views.oferta',
name='oferta'), name='oferta'),
url(r'^size/beuchat/$', 'main.views.size_beuchat', url(r'^size/beuchat/$', 'main.views.size_beuchat',
name='size_beuchat'), name='size_beuchat'),
url(r'^size/omer-sporasub/$', 'main.views.size_omer_sporasub', url(r'^size/omer-sporasub/$', 'main.views.size_omer_sporasub',
name='size_omer_sporasub'), name='size_omer_sporasub'),
url(r'^size/sargan/$', 'main.views.size_sargan', url(r'^size/sargan/$', 'main.views.size_sargan',
name='size_sargan'), name='size_sargan'),
url(r'^size/scorpena/$', 'main.views.size_scorpena', name='size_scorpena'), url(r'^size/scorpena/$', 'main.views.size_scorpena', name='size_scorpena'),
url(r'^size/aqua-discovery/$', 'main.views.size_aqua_discovery', url(r'^size/aqua-discovery/$', 'main.views.size_aqua_discovery',
name='size_aqua_discovery'), name='size_aqua_discovery'),
url(r'^size/aqualung/$', 'main.views.size_aqualung', url(r'^size/aqualung/$', 'main.views.size_aqualung',
name='size_aqualung'), name='size_aqualung'),
url(r'^size/aquatics/$', 'main.views.size_aquatics', url(r'^size/aquatics/$', 'main.views.size_aquatics',
name='size_aquatics'), name='size_aquatics'),
url(r'^size/cressi/$', 'main.views.size_cressi', url(r'^size/cressi/$', 'main.views.size_cressi',
name='size_cressi'), name='size_cressi'),
url(r'^size/mares/$', 'main.views.size_mares', url(r'^size/mares/$', 'main.views.size_mares',
name='size_mares'), name='size_mares'),
url(r'^size/elios/$', 'main.views.size_elios', url(r'^size/elios/$', 'main.views.size_elios',
name='size_elios'), name='size_elios'),
url(r'^api/item/(?P<article>.+)/$', 'main.views.temp_count_update', name='temp_count_update'), url(r'^api/item/(?P<article>.+)/$', 'main.views.temp_count_update', name='temp_count_update'),
url(r'^api/', include(router.urls)), url(r'^api/', include(router.urls)),
url(r'^news/', include('news.urls')), url(r'^news/', include('news.urls')),
url(r'^promo/', include('promo.urls')), url(r'^dtys/', include('dtys.urls')),
# url(r'^get_order_amount/$', 'store.views.get_order_amount'), url(r'^promo/', include('promo.urls')),
url(r'^get_order_kazpost_amount/$', 'store.views.get_order_kazpost_amount'), # url(r'^get_order_amount/$', 'store.views.get_order_amount'),
url(r'^order_call/$', 'api.views.call_order'), url(r'^get_order_kazpost_amount/$', 'store.views.get_order_kazpost_amount'),
url(r'^order_order/$', 'api.views.order_order'), url(r'^order_call/$', 'api.views.call_order'),
url(r'^order/(?P<order>.+)/print/$', order_print, url(r'^order_order/$', 'api.views.order_order'),
name='store_order_print'), url(r'^order/(?P<order>.+)/print/$', order_print,
url(r'^order/(?P<order>.+)/kkb/$', order_kkb, name='store_order_print'),
name='store_order_kkb'), url(r'^order/(?P<order>.+)/kkb/$', order_kkb,
url(r'^kkb_result/$', kkb_result, name='store_order_kkb'),
name='kkb_result'), url(r'^kkb_result/$', kkb_result,
name='kkb_result'),
url(r'^order/(?P<order>.+)/$', order_view,
name='store_order_view'), url(r'^order/(?P<order>.+)/$', order_view,
url(r'^get_dpd_cities/$', 'store.views.get_dpd_cities'), name='store_order_view'),
url(r'^get_order_dpd_amount/$', 'store.views.get_order_dpd_amount'), url(r'^get_dpd_cities/$', 'store.views.get_dpd_cities'),
url(r'^get_order_dpd_amount/$', 'store.views.get_order_dpd_amount'),
url(r'^store/', include('store.urls')),
url(r'^account/', include('accounts.urls')), url(r'^store/', include('store.urls')),
url(r'^advantages/$', advantages, name='advantages'), url(r'^account/', include('accounts.urls')),
url(r'^payment/$', payment, name='payment'), url(r'^advantages/$', advantages, name='advantages'),
url(r'^delivery/$', delivery, name='delivery'), url(r'^payment/$', payment, name='payment'),
url(r'^contacts/$', contacts, name='contacts'), url(r'^delivery/$', delivery, name='delivery'),
url(r'^faq/$', faq, name='faq'), url(r'^contacts/$', contacts, name='contacts'),
url(r'^fest/$', fest, name='fest'), url(r'^faq/$', faq, name='faq'),
url(r'^fest_success/$', fest_success, name='fest_success'), url(r'^fest/$', fest, name='fest'),
url(r'^currencies/(?P<currency_code>.+)/$', set_currency, name='set_currency'), url(r'^fest_success/$', fest_success, name='fest_success'),
url(r'^admin2517_garpun/', include(admin.site.urls)), url(r'^currencies/(?P<currency_code>.+)/$', set_currency, name='set_currency'),
) url(r'^admin2517_garpun/', include(admin.site.urls)),
]

@ -0,0 +1,10 @@
DTYS
Для возврата незаказанного товара нужно чтобы работал celery
Его нужно запустить со следующими параметрами:
celery -A dtys.tasks worker --loglevel=info
Выбранный (остановленный) товар возвращается на страницу после некоторого
количества секунд, указанного в settings.RESUME_DTYS_DELAY_SECONDS

@ -0,0 +1 @@
default_app_config = 'dtys.apps.DtysConfig'

@ -0,0 +1,14 @@
from django.contrib import admin
from .models import DTYSModel
@admin.register(DTYSModel)
class DTYSModelAdmin(admin.ModelAdmin):
raw_id_fields = ('product',)
list_display = (
'id',
'product',
'is_stopped',
'is_public'
)

@ -0,0 +1,8 @@
from django.apps import AppConfig
class DtysConfig(AppConfig):
"""Drop `Til You Shop"""
name = 'dtys'
verbose_name = "Поймай свою цену"

@ -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,65 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2017-11-03 21:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dtys', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='dtysmodel',
name='is_ordered',
field=models.BooleanField(default=False, verbose_name='Заказан'),
),
migrations.AlterField(
model_name='dtysmodel',
name='end_date',
field=models.DateField(verbose_name='Дата окончания'),
),
migrations.AlterField(
model_name='dtysmodel',
name='end_time',
field=models.TimeField(verbose_name='Время окончания'),
),
migrations.AlterField(
model_name='dtysmodel',
name='is_public',
field=models.BooleanField(default=False, verbose_name='Опубликован'),
),
migrations.AlterField(
model_name='dtysmodel',
name='is_stopped',
field=models.BooleanField(default=False, verbose_name='Остановлен'),
),
migrations.AlterField(
model_name='dtysmodel',
name='price_end',
field=models.IntegerField(verbose_name='Конечная цена (минимальная)'),
),
migrations.AlterField(
model_name='dtysmodel',
name='price_start',
field=models.IntegerField(verbose_name='Начальная цена'),
),
migrations.AlterField(
model_name='dtysmodel',
name='price_stopped',
field=models.IntegerField(default=0, verbose_name='Прайс заказа'),
),
migrations.AlterField(
model_name='dtysmodel',
name='start_date',
field=models.DateField(verbose_name='Дата начала'),
),
migrations.AlterField(
model_name='dtysmodel',
name='start_time',
field=models.TimeField(verbose_name='Время начала'),
),
]

@ -0,0 +1,95 @@
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):
"""Drop `Til You Shop Model"""
product = models.ForeignKey(ProductVariation)
price_start = models.IntegerField(verbose_name="Начальная цена")
price_end = models.IntegerField(verbose_name="Конечная цена (минимальная)")
price_stopped = models.IntegerField(verbose_name="Прайс заказа", default=0)
start_date = models.DateField(verbose_name='Дата начала')
start_time = models.TimeField(verbose_name='Время начала')
end_date = models.DateField(verbose_name='Дата окончания')
end_time = models.TimeField(verbose_name='Время окончания')
is_stopped = models.BooleanField(verbose_name="Остановлен", default=False)
is_public = models.BooleanField(verbose_name="Опубликован", default=False)
is_ordered = models.BooleanField(verbose_name="Заказан", default=False)
class Meta:
verbose_name = "Счётчик цены"
verbose_name_plural = "Счётчики цен"
@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_dec_by_sec(self):
return "%.1f" % (self.get_price_diff() / self.get_date_delta())
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 self.price_stopped == 0:
self.price_stopped = max(self.price_end, self.get_current_price())
self.save()
def save(self, *args, **kwargs):
if not self.id:
pass
ProductVariation.objects.filter(id=self.product.id)\
.update(has_discount=False)
super(DTYSModel, self).save(*args, **kwargs)

@ -0,0 +1,2 @@
celery=4.1.0
redis

@ -0,0 +1,11 @@
from celery import task
# app = Celery('tasks', broker='redis://localhost:6379/0')
@task
def return_dtys(dtys_id):
from .models import DTYSModel
DTYSModel.objects.filter(id=dtys_id, is_ordered=False)\
.update(is_stopped=False, price_stopped=0)

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,12 @@
from django.conf.urls import patterns, url
from .views import DTYSListView, DTYSInfoView, DTYSBuyView, dtys_modified
urlpatterns = patterns(
'',
url(r'^$', DTYSListView.as_view()),
url(r'^updates$', dtys_modified),
url(r'^info/$', DTYSInfoView.as_view()),
url(r'^buy/(?P<pk>\d+)/$', DTYSBuyView.as_view())
)

@ -0,0 +1,165 @@
import os
from django.conf import settings
from django.http import (
Http404,
HttpResponse,
HttpResponseRedirect,
JsonResponse,
)
from django.core.cache import cache
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__)
DTYS_UPDATE_FILE = os.path.join(settings.MEDIA_ROOT, "dtys_update")
def update_dtys_etag():
fhandle = open(DTYS_UPDATE_FILE, 'a')
try:
os.utime(DTYS_UPDATE_FILE, None)
except FileNotFoundError:
open(DTYS_UPDATE_FILE, 'a').close()
finally:
fhandle.close()
def latest_entry(request):
DTYSModel.objects.filter(
end_date__lte=timezone.now().date(),
end_time__lte=timezone.now().time()
).update(is_stopped=True)
d = DTYSModel.objects.filter(
is_public=True,
is_stopped=False
)
if d.count() != cache.get("dtysETAGCount"):
cache.set("dtysETAGCount", d.count())
update_dtys_etag()
if not os.path.exists(DTYS_UPDATE_FILE):
update_dtys_etag()
modified = str(os.path.getmtime(DTYS_UPDATE_FILE))
return modified
@etag(latest_entry)
def dtys_modified(request):
return HttpResponse('')
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()
)
if qs.count() == 0:
raise Http404()
return qs
class DTYSBuyView(DetailView):
model = DTYSModel
def get_queryset(self, *args, **kwargs):
qs = super(DTYSBuyView, 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()
)
if qs.count() == 0:
raise Http404()
return qs
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
from .tasks import return_dtys
update_dtys_etag()
# stop DTYS for this product
obj.stop()
ProductVariation.objects.filter(id=obj.product.id)\
.update(price=obj.price_stopped)
obj.product.price = int(obj.price_stopped)
obj.product.save()
# Add to cart
cart = Cart(self.request)
cart.add_item(CartItem(obj.product, 1))
return_dtys.apply_async(
(obj.id, ), countdown=settings.RESUME_DTYS_DELAY_SECONDS)
return HttpResponseRedirect('/store/cart/')
return HttpResponseRedirect('/dtys/')
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,
'stopped_price': i.price_stopped
}
if not i.is_stopped:
dtys_item['current_price'] = i.current_price_format()
dtys_item['price_dec_in_sec'] = i.get_dec_by_sec()
data['dtys_list'].append(dtys_item)
return JsonResponse(data)

@ -1,6 +1,7 @@
awesome-slugify==1.6.5 awesome-slugify==1.6.5
cffi==1.1.2 cffi==1.1.2
cryptography==0.9.3 cryptography==0.9.3
celery==4.1.0
Django==1.9.3 Django==1.9.3
django-jinja==1.4.1 django-jinja==1.4.1
django-jinja-bootstrap-form==4.0.3 django-jinja-bootstrap-form==4.0.3
@ -33,4 +34,4 @@ virtualenvwrapper==4.6.0
wcwidth==0.1.4 wcwidth==0.1.4
wheel==0.24.0 wheel==0.24.0
xmljson==0.1.5 xmljson==0.1.5
django-resized==0.3.5 django-resized==0.3.5

@ -0,0 +1,3 @@
#!/bin/bash
celery -A batiskaf worker --loglevel=info >> celery.log &

@ -0,0 +1,162 @@
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 countdownPrice(el, beginValue, decInSecond) {
var counter = el.find(".counter");
counter.text(beginValue);
if (el.attr("data-tid") ) {
var tid = el.attr("data-tid")
clearInterval(tid);
}
var delay = 1000;
var div = 2.0;
var timerId = setInterval(function(){
var val = parseFloat(counter.text());
var new_val = (val - (decInSecond / div)).toFixed();
counter.text(new_val);
}, delay / div);
el.attr("data-tid", timerId);
}
function countdownPriceStop(el) {
clearInterval(el.attr("data-tid"));
}
function get_price() {
$.ajax({
url: "/dtys/info/",
method: "GET",
success: function(data, textStatus, jqXHR) {
console.log(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();
product.find('.price').text(i.stopped_price);
countdownPriceStop(product);
} else {
product.removeClass("stopped");
product.find(".price").text(i.current_price);
fake_add_to_cart_btn.hide();
add_to_cart_btn.show();
countdownPrice(product, i.current_price, i.price_dec_in_sec);
}
});
}
},
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){
$(".dtys__product").each(function(){
$(this).removeClass("wait");
});
if(me.ETag != x.getResponseHeader('ETag')){
me.params.onModified(data,x,me.lastModified);
me.lastModified = x.getResponseHeader('Last-Modified');
}
me.ETag = x.getResponseHeader('ETag');
},
error: function(jqXHR, textStatus, errorThrown) {
$(".dtys__product").each(function(){
$(this).addClass("wait");
countdownPriceStop($(this));
});
},
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();
this.doUpdate();
};
};
$(function(){
var upd = new Updater();
get_price();
upd.init({
url: "/dtys/updates",
onModified: function(data, x, modified) {
get_price();
}
});
upd.start();
});

@ -936,3 +936,75 @@ a.btn {
color: @white!important; color: @white!important;
} }
} }
.dtys {
.stopped {
.sold-block {
opacity: 1;
bottom: 0;
&:hover {
opacity: 0.5;
}
}
}
.sold-block {
opacity: 0;
overflow: hidden;
border-radius: 4px;
cursor: not-allowed;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 500px;
z-index: 100;
transition: all .3s ease;
&__message {
font-size: 160%;
font-weight: bold;
color: white;
text-align: center;
padding-top: 20pt;
position: absolute;
top: 40%;
left: 0;
right: 0;
bottom: 0;
}
}
.wait-block {
display: none;
border: 1px solid gray;
border-radius: 4px;
background-color: rgba(0,0,0,0.2);
cursor: not-allowed;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 100;
.wait-icon {
display: inline-block;
position: absolute;
left: 50%;
top: 50%;
font-size: 160%;
color: white;
}
}
.wait {
.wait-block {
display: block;
}
}
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0044_auto_20151223_1612'),
]
operations = [
migrations.AlterField(
model_name='orderdata',
name='deliv_type',
field=models.CharField(max_length=2, blank=True, verbose_name='Способ Алемтат', choices=[('E', 'Экспресс-отправления по РК'), ('T', 'Не срочные отправления по РК')], null=True, default=''),
),
]

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0045_auto_20160107_1403'),
('store', '0045_auto_20151223_1646'),
]
operations = [
]

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0046_merge'),
('store', '0046_auto_20160202_1121'),
]
operations = [
]

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0047_merge'),
('store', '0047_auto_20160229_1409'),
]
operations = [
]

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0048_auto_20160302_0839'),
('store', '0048_merge'),
]
operations = [
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2016-03-07 20:44
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0049_merge'),
('store', '0049_auto_20160307_2043'),
]
operations = [
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2016-03-12 16:33
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0050_merge'),
('store', '0050_auto_20160312_1628'),
]
operations = [
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2016-03-14 11:41
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0051_cartmodel'),
('store', '0051_merge'),
]
operations = [
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2016-04-04 10:33
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0053_productvariation_delivery_date'),
('store', '0052_merge'),
]
operations = [
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2016-07-15 19:20
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0054_merge'),
('store', '0054_auto_20160715_1811'),
]
operations = [
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2017-03-07 15:49
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0057_auto_20170306_1933'),
('store', '0055_merge'),
]
operations = [
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2017-05-07 02:57
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0059_auto_20170506_1116'),
('store', '0058_merge'),
]
operations = [
]

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2017-06-03 02:27
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('store', '0060_merge'),
]
operations = [
migrations.AlterField(
model_name='orderdata',
name='city',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Город Алемтат'),
),
migrations.AlterField(
model_name='orderdata',
name='deliv_type',
field=models.CharField(blank=True, default='', max_length=2, null=True, verbose_name='Способ Алемтат'),
),
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2017-06-03 02:28
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0061_auto_20170603_0220'),
('store', '0061_auto_20170603_0227'),
]
operations = [
]

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2017-11-07 16:56
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('store', '0061_auto_20170603_0220'),
]
operations = [
migrations.AddField(
model_name='productvariation',
name='has_discount',
field=models.BooleanField(default=True, help_text='Если не указано, то скидка при расчёте цены не учитывается', verbose_name='Со скидкой'),
),
]

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.3 on 2017-11-07 18:10
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('store', '0062_productvariation_has_discount'),
('store', '0062_merge'),
]
operations = [
]

@ -209,7 +209,7 @@ class Product(models.Model):
try: try:
return self.images.order_by('-is_main')[0].image return self.images.order_by('-is_main')[0].image
except: except:
return None return {'url': ''}
def in_stock(self): def in_stock(self):
return self.variations.filter(in_stock__gt=0).count() return self.variations.filter(in_stock__gt=0).count()
@ -292,9 +292,22 @@ class ProductVariation(models.Model):
'В наличии (шт.)', default=1, null=False, blank=False) 'В наличии (шт.)', default=1, null=False, blank=False)
article = models.CharField( article = models.CharField(
'Артикул', max_length=32, null=True, blank=True, default='') 'Артикул', max_length=32, null=True, blank=True, default='')
weight = models.FloatField('Вес (кг)', default=0.1, null=False, blank=False) weight = models.FloatField(
discount = models.IntegerField('Скидка %', default=0, blank=False, null=False) verbose_name='Вес (кг)', default=0.1, null=False, blank=False)
delivery_date = models.CharField('Дата доставки', default=None, blank=True, null=True, max_length=20) discount = models.IntegerField(
verbose_name='Скидка %', default=0, blank=False, null=False)
delivery_date = models.CharField(
verbose_name='Дата доставки',
default=None,
blank=True,
null=True,
max_length=20
)
has_discount = models.BooleanField(
verbose_name='Со скидкой',
help_text="Если не указано, то скидка при расчёте цены не учитывается",
default=True
)
class Meta: class Meta:
verbose_name = 'разновидность товара' verbose_name = 'разновидность товара'
@ -307,8 +320,14 @@ class ProductVariation(models.Model):
# import pdb; pdb.set_trace() # import pdb; pdb.set_trace()
# from django.core.mail import mail_admins # from django.core.mail import mail_admins
# mail_admins('auth', '{} {}'.format(profile, self.product.brand.slug)) # mail_admins('auth', '{} {}'.format(profile, self.product.brand.slug))
if profile and profile.is_authenticated() and self.product.brand.slug in ['beuchat',
'scorpena'] and profile.sale: if not self.has_discount:
return int(self.price)
if profile\
and profile.is_authenticated()\
and self.product.brand.slug in ['beuchat', 'scorpena']\
and profile.sale:
return int(self.price - (self.price / Decimal(100) * Decimal(profile.sale))) return int(self.price - (self.price / Decimal(100) * Decimal(profile.sale)))
else: else:
if self.discount: if self.discount:

Loading…
Cancel
Save