diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9a2b0562..1d78b5d1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,16 +1,68 @@
stages:
- - deploy
+ - deploy
+ - db
+ - stop
+
+variables:
+ REVIEW_DOMAIN: back-review.lil.school
deploy_prod:
stage: deploy
script:
- rsync -a --stats --delete --exclude="docker/data/" --exclude="docker/.env" ./ /work/www/lil.school/
- cd /work/www/lil.school/docker/
- - docker-compose -f docker-compose-prod.yml up --build -d
+ - docker-compose -f docker-compose-prod.yml -p back up --build -d
environment:
- name: prod/site
+ name: prod
url: https://lil.school
only:
- master
tags:
- prod
+
+deploy_review:
+ stage: deploy
+ script:
+ - export REVIEW_HOST=$CI_COMMIT_REF_SLUG-$REVIEW_DOMAIN
+ - cd docker
+ - cp .env.review .env
+ - docker-compose -f docker-compose-review.yml -p back$CI_COMMIT_REF_NAME up --build -d
+ environment:
+ name: review/$CI_COMMIT_REF_SLUG
+ url: https://$CI_COMMIT_REF_SLUG-$REVIEW_DOMAIN
+ on_stop: stop-review
+ tags:
+ - review
+ only:
+ - branches
+
+stop-review:
+ stage: stop
+ environment:
+ name: review/$CI_COMMIT_REF_SLUG
+ action: stop
+ script:
+ - export REVIEW_HOST=$CI_COMMIT_REF_SLUG-$REVIEW_DOMAIN
+ - cd docker
+ - docker-compose -f docker-compose-review.yml -p back$CI_COMMIT_REF_NAME down
+ - rm -rf /work/data/back_${CI_COMMIT_REF_NAME}/
+ when: manual
+ only:
+ - branches
+ tags:
+ - review
+
+prod-db:
+ stage: db
+ script:
+ - export REVIEW_HOST=$CI_COMMIT_REF_SLUG-$REVIEW_DOMAIN
+ - cd docker
+ - cp .env.review .env
+ - docker-compose -f docker-compose-review.yml -p back$CI_COMMIT_REF_NAME restart db
+ - echo 'DROP DATABASE IF EXISTS lilcity; CREATE DATABASE lilcity' | docker-compose -f docker-compose-review.yml -p back$CI_COMMIT_REF_NAME exec -T -u postgres db psql postgres
+ - /work/scripts/get_prod_db.sh | docker-compose -f docker-compose-review.yml -p back$CI_COMMIT_REF_NAME exec -T -u postgres db psql lilcity
+ when: manual
+ only:
+ - branches
+ tags:
+ - review
diff --git a/api/v1/serializers/course.py b/api/v1/serializers/course.py
index 1a9695bb..bdb1a289 100644
--- a/api/v1/serializers/course.py
+++ b/api/v1/serializers/course.py
@@ -1,4 +1,5 @@
from rest_framework import serializers
+from rest_framework.validators import UniqueValidator
from apps.course.models import (
Category, Course,
@@ -88,7 +89,9 @@ class CourseCreateSerializer(DispatchContentMixin,
):
title = serializers.CharField(allow_blank=True)
short_description = serializers.CharField(allow_blank=True)
- slug = serializers.SlugField(allow_unicode=True, allow_blank=True, allow_null=True, required=False)
+ slug = serializers.SlugField(
+ allow_unicode=True, allow_blank=True, allow_null=True,
+ required=False, validators=[UniqueValidator(queryset=Course.objects.all())])
content = serializers.ListSerializer(
child=ContentCreateSerializer(),
required=False,
diff --git a/apps/payment/admin.py b/apps/payment/admin.py
index 47dde452..19166563 100644
--- a/apps/payment/admin.py
+++ b/apps/payment/admin.py
@@ -28,10 +28,11 @@ class PaymentChildAdmin(PolymorphicChildModelAdmin):
'user',
'amount',
'status',
+ 'roistat_visit',
'created_at',
)
base_fieldsets = (
- (None, {'fields': ('user', 'amount', 'status', 'data',)}),
+ (None, {'fields': ('user', 'amount', 'status', 'data', 'roistat_visit',)}),
)
readonly_fields = ('amount', 'data',)
diff --git a/apps/payment/management/__init__.py b/apps/payment/management/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/apps/payment/management/commands/__init__.py b/apps/payment/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/apps/payment/management/commands/roistat_set_statuses.py b/apps/payment/management/commands/roistat_set_statuses.py
new file mode 100644
index 00000000..f3ac3e11
--- /dev/null
+++ b/apps/payment/management/commands/roistat_set_statuses.py
@@ -0,0 +1,50 @@
+import requests
+
+from paymentwall import Pingback
+
+from django.conf import settings
+from django.core.management.base import BaseCommand, CommandError
+
+
+class Command(BaseCommand):
+ help = 'Update statuses on Roistat'
+
+ def handle(self, *args, **options):
+ body = [
+ {
+ 'id': str(Pingback.PINGBACK_TYPE_REGULAR),
+ 'name': 'PINGBACK_TYPE_REGULAR',
+ 'type': 'paid',
+ },
+ {
+ 'id': str(Pingback.PINGBACK_TYPE_GOODWILL),
+ 'name': 'PINGBACK_TYPE_GOODWILL',
+ 'type': 'paid',
+ },
+ {
+ 'id': str(Pingback.PINGBACK_TYPE_NEGATIVE),
+ 'name': 'PINGBACK_TYPE_NEGATIVE',
+ 'type': 'canceled',
+ },
+ {
+ 'id': str(Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW),
+ 'name': 'PINGBACK_TYPE_RISK_UNDER_REVIEW',
+ 'type': 'progress',
+ },
+ {
+ 'id': str(Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED),
+ 'name': 'PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED',
+ 'type': 'paid',
+ },
+ {
+ 'id': str(Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED),
+ 'name': 'PINGBACK_TYPE_RISK_REVIEWED_DECLINED',
+ 'type': 'canceled',
+ },
+ ]
+ project = settings.ROISTAT_PROJECT
+ key = settings.ROISTAT_KEY
+ url = settings.ROISTAT_API_URL + f'/project/set-statuses?key={key}&project={project}'
+ resp = requests.post(url, json=body)
+ self.stdout.write(str(resp))
+ self.stdout.write(str(resp.json()))
diff --git a/apps/payment/migrations/0019_payment_roistat_visit.py b/apps/payment/migrations/0019_payment_roistat_visit.py
new file mode 100644
index 00000000..4f7fa40e
--- /dev/null
+++ b/apps/payment/migrations/0019_payment_roistat_visit.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.7 on 2018-07-06 07:53
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('payment', '0018_auto_20180512_1202'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='payment',
+ name='roistat_visit',
+ field=models.PositiveIntegerField(editable=False, null=True, verbose_name='Номер визита Roistat'),
+ ),
+ ]
diff --git a/apps/payment/models.py b/apps/payment/models.py
index b1368dc4..32d7a0e9 100644
--- a/apps/payment/models.py
+++ b/apps/payment/models.py
@@ -97,6 +97,7 @@ class Payment(PolymorphicModel):
amount = models.DecimalField('Итого', max_digits=8, decimal_places=2, default=0, editable=False)
status = models.PositiveSmallIntegerField('Статус платежа', choices=PW_STATUS_CHOICES, null=True)
data = JSONField('Данные платежа от провайдера', default={}, editable=False)
+ roistat_visit = models.PositiveIntegerField('Номер визита Roistat', null=True, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
diff --git a/apps/payment/tasks.py b/apps/payment/tasks.py
index 3f487f76..e30f4e19 100644
--- a/apps/payment/tasks.py
+++ b/apps/payment/tasks.py
@@ -1,9 +1,14 @@
+import logging
+import requests
+
from mixpanel import Mixpanel
from django.conf import settings
from project.celery import app
+logger = logging.getLogger(__name__)
+
@app.task
def transaction_to_mixpanel(user_id, amount, time, product_type):
@@ -30,3 +35,24 @@ def product_payment_to_mixpanel(user_id, event_name, time, properties):
event_name,
properties=props,
)
+
+
+@app.task
+def transaction_to_roistat(user_id, payment_id, event_name, amount, time, status, product_type, roistat_visit):
+ body = [{
+ 'id': str(payment_id),
+ 'name': event_name,
+ 'date_create': time,
+ 'status': str(status),
+ 'roistat': str(roistat_visit) if roistat_visit else None,
+ 'price': str(amount),
+ 'client_id': str(user_id),
+ 'fields': {
+ 'product_type': product_type,
+ }
+ }]
+ project = settings.ROISTAT_PROJECT
+ key = settings.ROISTAT_KEY
+ url = settings.ROISTAT_API_URL + f'/project/add-orders?key={key}&project={project}'
+ resp = requests.post(url, json=body)
+ logger.info('TRANSACTION_TO_ROISTAT: ' + str(resp))
diff --git a/apps/payment/views.py b/apps/payment/views.py
index c9186a25..4f4a958a 100644
--- a/apps/payment/views.py
+++ b/apps/payment/views.py
@@ -22,7 +22,7 @@ from paymentwall import Pingback, Product, Widget
from apps.course.models import Course
from apps.school.models import SchoolSchedule
-from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel
+from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel, transaction_to_roistat
from .models import AuthorBalance, CoursePayment, SchoolPayment
@@ -58,12 +58,14 @@ class CourseBuyView(TemplateView):
host = urlsplit(self.request.META.get('HTTP_REFERER'))
host = str(host[0]) + '://' + str(host[1])
course = Course.objects.get(id=pk)
+ roistat_visit = request.COOKIES.get('roistat_visit', None)
if request.user == course.author:
messages.error(request, 'Вы не можете приобрести свой курс.')
return redirect(reverse_lazy('course', args=[course.id]))
course_payment = CoursePayment.objects.create(
user=request.user,
course=course,
+ roistat_visit=roistat_visit,
)
product = Product(
f'course_{course_payment.id}',
@@ -96,6 +98,7 @@ class SchoolBuyView(TemplateView):
host = str(host[0]) + '://' + str(host[1])
weekdays = set(request.GET.getlist('weekdays', []))
add_days = 'add_days' in request.GET
+ roistat_visit = request.COOKIES.get('roistat_visit', None)
if not weekdays:
messages.error(request, 'Выберите несколько дней недели.')
return redirect('school:summer-school')
@@ -117,6 +120,7 @@ class SchoolBuyView(TemplateView):
date_start=now().date(),
date_end=_school_payment.date_end,
add_days=True,
+ roistat_visit=roistat_visit,
)
if school_payment.amount <= 0:
messages.error(request, 'Выбранные дни отсутствуют в оставшемся периоде подписки')
@@ -125,6 +129,7 @@ class SchoolBuyView(TemplateView):
school_payment = SchoolPayment.objects.create(
user=request.user,
weekdays=weekdays,
+ roistat_visit=roistat_visit,
)
product = Product(
f'school_{school_payment.id}',
@@ -251,6 +256,16 @@ class PaymentwallCallbackView(View):
properties,
)
+ transaction_to_roistat.delay(
+ payment.user.id,
+ payment.id,
+ f'{product_type_name.title()} payment',
+ payment.amount,
+ now().strftime('%Y-%m-%d %H:%M:%S'),
+ pingback.get_type(),
+ product_type_name,
+ payment.roistat_visit,
+ )
author_balance = getattr(payment, 'author_balance', None)
if author_balance and author_balance.type == AuthorBalance.IN:
if pingback.is_deliverable():
diff --git a/apps/school/models.py b/apps/school/models.py
index 24ce0590..91d007e8 100644
--- a/apps/school/models.py
+++ b/apps/school/models.py
@@ -1,6 +1,9 @@
+import arrow
from datetime import datetime, timedelta
+from django.conf import settings
from django.db import models
+from django.urls import reverse_lazy
from django.utils.timezone import now
from project.mixins import BaseModel, DeactivatedMixin
@@ -65,6 +68,10 @@ class SchoolSchedule(models.Model):
).first()
return live_lesson
+ @property
+ def start_at_humanize(self):
+ return arrow.get(datetime.combine(datetime.today(), self.start_at), settings.TIME_ZONE).humanize(locale='ru') if self.start_at else None
+
class SchoolScheduleImage(models.Model):
schoolschedule = models.ForeignKey(
@@ -121,6 +128,9 @@ class LiveLesson(BaseModel, DeactivatedMixin):
self.date = (datetime.combine(self.date, now().time()) + timedelta(days=1)).date()
super().save(*args, **kwargs)
+ def get_absolute_url(self):
+ return reverse_lazy('school:lesson-detail', args=[str(self.id)])
+
def stream_index(self):
return self.stream.split('/')[-1]
diff --git a/apps/school/templates/blocks/_schedule_purchased_item.html b/apps/school/templates/blocks/_schedule_purchased_item.html
index 416a4f4f..b9d1db1f 100644
--- a/apps/school/templates/blocks/_schedule_purchased_item.html
+++ b/apps/school/templates/blocks/_schedule_purchased_item.html
@@ -6,15 +6,17 @@
{{ school_schedule }}
{% if live_lesson %}
-
+
{{ live_lesson.date }}
{% endif %}
{{ school_schedule.start_at }} (МСК)
- {% if school_schedule.weekday in school_schedules_purchased and live_lesson %}
- {% include './open_lesson.html' %}
+ {% if school_schedule.weekday in school_schedules_purchased %}
+ {% if live_lesson and live_lesson.title %}
+ {% include './open_lesson.html' %}
+ {% endif %}
{% else %}
- {% include './day_pay_btn.html' %}
+ {% include './day_pay_btn.html' %}
{% endif %}
{% comment %}
@@ -34,12 +36,12 @@
-
{{ school_schedule.title }}{% if live_lesson %},
+
{{ school_schedule.title }}{% if live_lesson and live_lesson.title %},
{{ live_lesson.title }}
{% endif %}
- {% if live_lesson %}
+ {% if live_lesson and live_lesson.short_description %}
{{ live_lesson.short_description }}
{% else %}
{{ school_schedule.description }}
diff --git a/apps/school/templates/summer/_schedule_purchased_item.html b/apps/school/templates/summer/_schedule_purchased_item.html
index 416a4f4f..b9d1db1f 100644
--- a/apps/school/templates/summer/_schedule_purchased_item.html
+++ b/apps/school/templates/summer/_schedule_purchased_item.html
@@ -6,15 +6,17 @@
{{ school_schedule }}
{% if live_lesson %}
-
+
{{ live_lesson.date }}
{% endif %}
{{ school_schedule.start_at }} (МСК)
- {% if school_schedule.weekday in school_schedules_purchased and live_lesson %}
- {% include './open_lesson.html' %}
+ {% if school_schedule.weekday in school_schedules_purchased %}
+ {% if live_lesson and live_lesson.title %}
+ {% include './open_lesson.html' %}
+ {% endif %}
{% else %}
- {% include './day_pay_btn.html' %}
+ {% include './day_pay_btn.html' %}
{% endif %}
{% comment %}
@@ -34,12 +36,12 @@
-
{{ school_schedule.title }}{% if live_lesson %},
+
{{ school_schedule.title }}{% if live_lesson and live_lesson.title %},
{{ live_lesson.title }}
{% endif %}
- {% if live_lesson %}
+ {% if live_lesson and live_lesson.short_description %}
{{ live_lesson.short_description }}
{% else %}
{{ school_schedule.description }}
diff --git a/apps/user/management/__init__.py b/apps/user/management/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/apps/user/management/commands/__init__.py b/apps/user/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/apps/user/management/commands/users_to_roistat.py b/apps/user/management/commands/users_to_roistat.py
new file mode 100644
index 00000000..84ae366e
--- /dev/null
+++ b/apps/user/management/commands/users_to_roistat.py
@@ -0,0 +1,32 @@
+import requests
+
+from django.conf import settings
+from django.contrib.auth import get_user_model
+from django.core.management.base import BaseCommand, CommandError
+
+# User = get_user_model()
+
+from apps.user.models import User
+
+
+class Command(BaseCommand):
+ help = 'Upload users to Roistat'
+
+ def handle(self, *args, **options):
+ users_queryset = User.objects.all()
+ users = [
+ {
+ 'id': str(user.id),
+ 'name': user.get_full_name(),
+ 'phone': str(user.phone),
+ 'email': user.email,
+ 'birth_date': user.birthday.strftime('%d%m%Y') if user.birthday else None,
+ }
+ for user in users_queryset
+ ]
+ project = settings.ROISTAT_PROJECT
+ key = settings.ROISTAT_KEY
+ url = settings.ROISTAT_API_URL + f'/project/clients/import?key={key}&project={project}'
+ resp = requests.post(url, json=users)
+ self.stdout.write(str(resp))
+ self.stdout.write(str(resp.json()))
diff --git a/apps/user/tasks.py b/apps/user/tasks.py
index a95988be..e35accef 100644
--- a/apps/user/tasks.py
+++ b/apps/user/tasks.py
@@ -1,6 +1,9 @@
+import requests
+
from mixpanel import Mixpanel
from django.conf import settings
+from django.contrib.auth import get_user_model
from project.celery import app
@@ -20,3 +23,23 @@ def user_to_mixpanel(user_id, email, phone, first_name, last_name, date_joined,
'subscriptions': subscriptions,
}
)
+
+
+@app.task
+def users_to_roistat():
+ User = get_user_model()
+ users_queryset = User.objects.all()
+ users = [
+ {
+ 'id': str(user.id),
+ 'name': user.get_full_name(),
+ 'phone': str(user.phone),
+ 'email': user.email,
+ 'birth_date': user.birthday.strftime('%d%m%Y') if user.birthday else None,
+ }
+ for user in users_queryset
+ ]
+ project = settings.ROISTAT_PROJECT
+ key = settings.ROISTAT_KEY
+ url = settings.ROISTAT_API_URL + f'/project/clients/import?key={key}&project={project}'
+ resp = requests.post(url, json=users)
diff --git a/docker/.env.example b/docker/.env.example
index bc750b18..94fdccb7 100644
--- a/docker/.env.example
+++ b/docker/.env.example
@@ -20,3 +20,5 @@ PAYMENTWALL_SECRET_KEY=4ea515bf94e34cf28646c2e12a7b8707
MIXPANEL_TOKEN=79bd6bfd98667ed977737e6810b8abcd
RAVEN_DSN=https://b545dac0ae0545a1bcfc443326fe5850:6f9c900cef7f4c11b63561030b37d15c@sentry.io/1197254
ROISTAT_COUNTER_ID=09db30c750035ae3d70a41d5f10d59ec
+ROISTAT_PROJECT=84418
+ROISTAT_KEY=a2d82a254478c1727adf0eb7ceb669f5
diff --git a/docker/.env.review b/docker/.env.review
new file mode 100644
index 00000000..bc750b18
--- /dev/null
+++ b/docker/.env.review
@@ -0,0 +1,22 @@
+# DEBUG=True
+ALLOWED_HOSTS=*
+PORT=8000
+CORS_ORIGIN_WHITELIST=lilcity.9ev.ru:8080
+LANG=ru_RU.UTF-8
+POSTGRES_DB=lilcity
+POSTGRES_USER=lilcity
+POSTGRES_PASSWORD=GPVs/E/{5&qe
+DJANGO_SETTINGS_MODULE=project.settings
+DATABASE_SERVICE_HOST=db
+SECRET_KEY=jelm*91lj(_-o20+6^a+bgv!4s6e_efry^#+f#=1ak&s1xr-2j
+MAILGUN_API_KEY=key-ec6af2d43d031d59bff6b1c8fb9390cb
+MAILGUN_SENDER_DOMAIN=mail.9ev.ru
+DEFAULT_FROM_EMAIL=postmaster@mail.9ev.ru
+TWILIO_ACCOUNT=ACdf4a96b776cc764bc3ec0f0e136ba550
+TWILIO_TOKEN=559a6b1fce121759c9af2dcbb3f755ea
+TWILIO_FROM_PHONE=+37128914409
+PAYMENTWALL_APP_KEY=d6f02b90cf6b16220932f4037578aff7
+PAYMENTWALL_SECRET_KEY=4ea515bf94e34cf28646c2e12a7b8707
+MIXPANEL_TOKEN=79bd6bfd98667ed977737e6810b8abcd
+RAVEN_DSN=https://b545dac0ae0545a1bcfc443326fe5850:6f9c900cef7f4c11b63561030b37d15c@sentry.io/1197254
+ROISTAT_COUNTER_ID=09db30c750035ae3d70a41d5f10d59ec
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 46722a2f..acaa74c8 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -18,4 +18,5 @@ ADD . /app/
COPY --from=front /web/build/ /app/web/build/
RUN python manage.py collectstatic --no-input
RUN rm -rf /etc/nginx/ && cp -r docker/conf/nginx /etc/ && cp -r docker/conf/supervisor/* /etc/supervisor/conf.d/ && chown -R www-data:www-data /app/
+EXPOSE 80
ENTRYPOINT ["/app/docker/entrypoint_app.sh"]
diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev
new file mode 100644
index 00000000..9ae8465a
--- /dev/null
+++ b/docker/Dockerfile.dev
@@ -0,0 +1,18 @@
+FROM node:9.11.1-alpine as front
+RUN apk update && apk add python alpine-sdk --no-cache
+WORKDIR /web/
+ADD ./web/yarn.lock /web/
+ADD ./web/package.json /web/
+RUN yarn install
+ADD ./web/ /web/
+RUN yarn build
+
+FROM python:3.6
+ENV PYTHONUNBUFFERED 1
+RUN mkdir /app
+WORKDIR /app
+ADD requirements.txt /app/
+RUN pip install --no-cache-dir -r requirements.txt
+ADD . /app/
+COPY --from=front /web/build/ /app/web/build/
+RUN python manage.py collectstatic --no-input
diff --git a/docker/conf/nginx/sites-available/default b/docker/conf/nginx/conf.d/default.conf
similarity index 71%
rename from docker/conf/nginx/sites-available/default
rename to docker/conf/nginx/conf.d/default.conf
index 1aaa7519..b20174f6 100644
--- a/docker/conf/nginx/sites-available/default
+++ b/docker/conf/nginx/conf.d/default.conf
@@ -19,9 +19,19 @@ server {
location /media/instagram/results/ {
expires 1d;
+ try_files $uri @prod;
}
+ try_files $uri @prod;
}
+ location @prod {
+ if ($host = "lil.school") {
+ return 404;
+ }
+ proxy_pass https://lil.school;
+ proxy_buffering off;
+ }
+
location / {
try_files $uri @django;
}
diff --git a/docker/conf/nginx/nginx.conf b/docker/conf/nginx/nginx.conf
index c477630e..2889f7ad 100644
--- a/docker/conf/nginx/nginx.conf
+++ b/docker/conf/nginx/nginx.conf
@@ -26,7 +26,6 @@ http {
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
- include /etc/nginx/sites-enabled/*;
set_real_ip_from 192.168.0.0/24;
}
diff --git a/docker/conf/nginx/sites-enabled/default b/docker/conf/nginx/sites-enabled/default
deleted file mode 120000
index ad35b834..00000000
--- a/docker/conf/nginx/sites-enabled/default
+++ /dev/null
@@ -1 +0,0 @@
-/etc/nginx/sites-available/default
\ No newline at end of file
diff --git a/docker/docker-compose-dev.yml_notwork b/docker/docker-compose-dev.yml
similarity index 57%
rename from docker/docker-compose-dev.yml_notwork
rename to docker/docker-compose-dev.yml
index 4de5b13a..9b636ee2 100644
--- a/docker/docker-compose-dev.yml_notwork
+++ b/docker/docker-compose-dev.yml
@@ -2,27 +2,32 @@ version: '3'
services:
db:
- image: postgres:10-alpine
+ image: postgres:alpine
+ restart: always
env_file:
- .env
ports:
- "127.0.0.1:5432:5432"
volumes:
- - ./postgres_data:/var/lib/postgresql/data/pgdata
+ - ./data/postgres_dev:/var/lib/postgresql/data
redis:
- image: redis:latest
+ image: redis:alpine
+ restart: always
ports:
- "127.0.0.1:6379:6379"
volumes:
- - ./redis_data:/data
+ - ./data/redis_dev:/data
web:
- build: .
+ build:
+ context: ../
+ dockerfile: docker/Dockerfile.dev
restart: always
volumes:
- - .:/lilcity
- command: bash -c "python manage.py collectstatic --no-input && python manage.py migrate && python manage.py loaddata /lilcity/apps/*/fixtures/*.json && gunicorn --workers=4 project.wsgi --bind=0.0.0.0:8000 --worker-class=gthread --reload"
+ - ..:/app
+ - ./data/media:/app/media
+ command: bash -c "python manage.py collectstatic --no-input && python manage.py migrate && gunicorn --workers=4 project.wsgi --bind=0.0.0.0:8000 --worker-class=gthread --reload"
env_file:
- .env
ports:
@@ -35,10 +40,13 @@ services:
- redis
workers:
- build: .
+ build:
+ context: ../
+ dockerfile: docker/Dockerfile.dev
restart: always
volumes:
- - .:/lilcity
+ - ..:/app
+ - ./data/media:/app/media
command: bash -c "celery worker -A project -B"
env_file:
- .env
diff --git a/docker/docker-compose-prod.yml b/docker/docker-compose-prod.yml
index 440f2cbf..c3b97c9e 100644
--- a/docker/docker-compose-prod.yml
+++ b/docker/docker-compose-prod.yml
@@ -10,6 +10,11 @@ services:
- .env
volumes:
- ./data/postgres:/var/lib/postgresql/data
+ logging: &logging
+ driver: "json-file"
+ options:
+ max-size: "1m"
+ max-file: "1"
redis:
image: redis:4.0.9-alpine
@@ -18,6 +23,7 @@ services:
- "127.0.0.1:6379:6379"
volumes:
- ./data/redis:/data
+ logging: *logging
app:
build:
@@ -36,3 +42,4 @@ services:
links:
- db
- redis
+ logging: *logging
diff --git a/docker/docker-compose-review.yml b/docker/docker-compose-review.yml
new file mode 100644
index 00000000..9413f978
--- /dev/null
+++ b/docker/docker-compose-review.yml
@@ -0,0 +1,58 @@
+version: '3'
+
+services:
+ db:
+ image: postgres:10.3-alpine
+ restart: always
+ env_file:
+ - .env
+ volumes:
+ - /work/data/back_${CI_COMMIT_REF_NAME}/postgres:/var/lib/postgresql/data
+ logging: &logging
+ driver: "json-file"
+ options:
+ max-size: "1m"
+ max-file: "1"
+ networks:
+ - internal
+ - review
+ labels:
+ - traefik.enable=false
+
+ redis:
+ image: redis:4.0.9-alpine
+ restart: always
+ volumes:
+ - /work/data/back_${CI_COMMIT_REF_NAME}/redis:/data
+ logging: *logging
+ networks:
+ - internal
+ - review
+ labels:
+ - traefik.enable=false
+
+ app:
+ build:
+ context: ../
+ dockerfile: docker/Dockerfile
+ restart: always
+ env_file:
+ - .env
+ volumes:
+ - /work/data/back_${CI_COMMIT_REF_NAME}/media:/app/media
+ depends_on:
+ - db
+ - redis
+ logging: *logging
+ networks:
+ - internal
+ - review
+ labels:
+ - traefik.frontend.rule=Host:${REVIEW_HOST}
+ - traefik.docker.network=review
+
+networks:
+ internal:
+ review:
+ external:
+ name: review
diff --git a/docker/entrypoint_app.sh b/docker/entrypoint_app.sh
index a61879d2..a346f89c 100755
--- a/docker/entrypoint_app.sh
+++ b/docker/entrypoint_app.sh
@@ -1,5 +1,6 @@
#!/bin/sh
cd /app
+chown www-data:www-data /app/media
python manage.py migrate
#python manage.py loaddata /app/apps/*/fixtures/*.json
python2.7 /usr/bin/supervisord -n
diff --git a/project/settings.py b/project/settings.py
index b8b749d2..9323745c 100644
--- a/project/settings.py
+++ b/project/settings.py
@@ -44,6 +44,7 @@ INSTALLED_APPS = [
] + [
'anymail',
'active_link',
+ 'compressor',
'django_filters',
'polymorphic_tree',
'polymorphic',
@@ -163,6 +164,11 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'web/build'),
]
+STATICFILES_FINDERS = [
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+ 'compressor.finders.CompressorFinder',
+]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
@@ -224,6 +230,11 @@ CELERY_BEAT_SCHEDULE = {
'schedule': timedelta(minutes=5) if DEBUG else crontab(minute=0, hour=0),
'args': (),
},
+ 'update_users_in_roistat': {
+ 'task': 'apps.user.tasks.users_to_roistat',
+ 'schedule': timedelta(hours=1),
+ 'args': (),
+ },
}
try:
@@ -260,12 +271,17 @@ RAVEN_CONFIG = {
}
# Roistat counter id
+ROISTAT_API_URL = 'https://cloud.roistat.com/api/v1'
ROISTAT_COUNTER_ID = os.getenv('ROISTAT_COUNTER_ID', None)
+ROISTAT_PROJECT = os.getenv('ROISTAT_PROJECT', None)
+ROISTAT_KEY = os.getenv('ROISTAT_KEY', None)
INSTAGRAM_RESULTS_PATH = 'media/instagram/results/'
DATA_UPLOAD_MAX_MEMORY_SIZE = 20242880
+COMPRESS_ENABLED = True
+
try:
from .local_settings import *
except ImportError:
diff --git a/project/templates/blocks/about.html b/project/templates/blocks/about.html
index 4caf08de..8deec906 100644
--- a/project/templates/blocks/about.html
+++ b/project/templates/blocks/about.html
@@ -44,21 +44,19 @@
diff --git a/project/templates/blocks/promo.html b/project/templates/blocks/promo.html
index 53b7f7a9..4e49ee7a 100644
--- a/project/templates/blocks/promo.html
+++ b/project/templates/blocks/promo.html
@@ -11,31 +11,61 @@
Lil School — первая образовательная онлайн-платформа креативного мышления для детей
-
- Урок
Рисовальный лагерь, Альбрехт Дюрер завершен
- Следующий урок пройдет 4 июля
+ {% if user.is_authenticated and online %}
+
+ Сейчас идёт прямой эфир урока «{{ school_schedule.title }}, {{ school_schedule.current_live_lesson.title }}»
+ {% elif user.is_authenticated and online_coming_soon and school_schedule and school_schedule.start_at_humanize %}
+
+ Урок «{{ school_schedule.title }}, {{ school_schedule.current_live_lesson.title }}» начнётся
+
+
+ {{ school_schedule.start_at_humanize }}
+
+
+ {% else %}
+
+ Присоединяйтесь в Рисовальный лагерь
+
+
+ {% endif %}
diff --git a/project/templates/lilcity/index.html b/project/templates/lilcity/index.html
index 7f3fed4e..30b5541a 100644
--- a/project/templates/lilcity/index.html
+++ b/project/templates/lilcity/index.html
@@ -1,5 +1,6 @@
{% load static %}
{% load setting from settings %}
+{% load compress %}
@@ -24,14 +25,18 @@
{% comment %} {% endcomment %}
-
+
{% comment %} {% endcomment %}
-
+
+ {% compress css %}
+
+ {% endcompress %}
+
+
+
+
+
+
+
+
{% include "templates/blocks/mixpanel.html" %}
@@ -75,8 +100,7 @@
{% comment %} ROISTAT {% endcomment %}
{% block foot %}{% endblock foot %}
diff --git a/project/views.py b/project/views.py
index 6e4eca8d..c8400f14 100644
--- a/project/views.py
+++ b/project/views.py
@@ -27,6 +27,8 @@ class IndexView(TemplateView):
school_schedule = SchoolSchedule.objects.get(weekday=now_time.isoweekday())
except SchoolSchedule.DoesNotExist:
online = False
+ online_coming_soon = False
+ school_schedule = None
else:
end_at = datetime.combine(now_time.today(), school_schedule.start_at)
online = (
@@ -34,6 +36,14 @@ class IndexView(TemplateView):
(end_at + timedelta(hours=1)).time() >= now_time.time() and
school_schedule.current_live_lesson()
)
+ online_coming_soon = (
+ school_schedule.start_at > now_time.time() and
+ (
+ datetime.combine(datetime.today(), school_schedule.start_at) - timedelta(hours=12)
+ ).time() <= now_time.time() and
+ school_schedule.current_live_lesson()
+ )
+
date_now = now_time.date()
if self.request.user.is_authenticated:
school_payment = SchoolPayment.objects.filter(
@@ -62,6 +72,8 @@ class IndexView(TemplateView):
context.update({
'online': online,
+ 'online_coming_soon': online_coming_soon,
+ 'school_schedule': school_schedule,
'course_items': Course.objects.filter(status=Course.PUBLISHED)[:6],
'is_purchased': school_payment_exists,
'min_school_price': SchoolSchedule.objects.aggregate(Min('month_price'))['month_price__min'],
diff --git a/requirements.txt b/requirements.txt
index 7b283f09..05894ddd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,26 +1,26 @@
# Python-3.6
arrow==0.12.1
celery[redis]==4.2.0
-Django==2.0.6
+Django==2.0.7
django-active-link==0.1.5
django-anymail[mailgun]==3.0
django-cors-headers==2.3.0
+django_compressor==2.2
django-filter==2.0.0.dev1
django-mptt==0.9.0
-django-silk==3.0.0
django-phonenumber-field==2.0.0
django-polymorphic-tree==1.5
djangorestframework==3.8.2
-drf-yasg[validation]==1.9.0
+drf-yasg==1.9.0
facepy==1.0.9
-gunicorn==19.8.1
+gunicorn==19.9.0
mixpanel==4.3.2
psycopg2-binary==2.7.5
-Pillow==5.1.0
+Pillow==5.2.0
raven==6.9.0
requests==2.19.1
sorl-thumbnail==12.4.1
-twilio==6.14.6
+twilio==6.14.7
# paymentwall-python==1.0.7
git+https://github.com/ivlevdenis/paymentwall-python.git
# python-instagram==1.3.2
diff --git a/web/src/components/CourseRedactor.vue b/web/src/components/CourseRedactor.vue
index 4407671c..f4291156 100644
--- a/web/src/components/CourseRedactor.vue
+++ b/web/src/components/CourseRedactor.vue
@@ -620,6 +620,14 @@
$(window).scrollTop(elementTop);
});
},
+ processCourseJson(data) {
+ this.course = api.convertCourseJson(data);
+ this.course.live = this.live;
+ this.lessons = data.lessons.map((lessonJson) => {
+ return api.convertLessonJson(lessonJson);
+ });
+ this.course.duration = this.course.duration || '';
+ },
loadCourseDraft() {
//console.log('loadCourseDraft');
if(this.live) { return; }
@@ -629,12 +637,8 @@
response
.then((response) => {
- this.course = api.convertCourseJson(response.data);
- this.course.live = this.live;
+ this.processCourseJson(response.data);
this.courseLoading = false;
- this.lessons = response.data.lessons.map((lessonJson) => {
- return api.convertLessonJson(lessonJson);
- });
})
.catch((err) => {
this.courseLoading = false;
@@ -654,21 +658,10 @@
}
request
.then((response) => {
- this.course = api.convertCourseJson(response.data);
- this.course.live = this.live;
-
- /* if (this.live && this.course.date) {
- this.course.date = _.find(this.scheduleOptions, (item) => {
- return item.value == this.course.date;
- })
- } */
-
+ this.processCourseJson(response.data);
this.$nextTick(() => {
this.courseLoading = false;
});
- this.lessons = response.data.lessons.map((lessonJson) => {
- return api.convertLessonJson(lessonJson);
- });
this.lessons.sort((a, b) => {
if (a.position > b.position) {
return 1;
@@ -1060,7 +1053,7 @@
},
displayPrice: {
get: function () {
- return this.course.is_paid ? (this.course.price || 0) : 0;
+ return this.course.is_paid ? (this.course.price || '') : '';
},
set: function (value) {
this.course.price = value || 0;
diff --git a/web/src/js/modules/api.js b/web/src/js/modules/api.js
index 3b48837b..990ac7b2 100644
--- a/web/src/js/modules/api.js
+++ b/web/src/js/modules/api.js
@@ -107,9 +107,9 @@ export const api = {
author: courseObject.author ? courseObject.author : null,
short_description: courseObject.short_description,
category: courseObject.category,
- price: courseObject.is_paid ? courseObject.price : 0,
+ price: courseObject.is_paid && courseObject.price || 0,
deferred_start_at: deferredStart,
- duration: courseObject.duration,
+ duration: courseObject.duration || 0,
is_featured: courseObject.is_featured,
slug: courseObject.url,
date: (courseObject.date) ? courseObject.date.value:null,