Merge remote-tracking branch 'origin/dev' into dev

remotes/origin/hasaccess
Vitaly Baev 8 years ago
commit 95047508db
  1. 1
      .dockerignore
  2. 7
      api/v1/serializers/config.py
  3. 56
      apps/course/templates/course/course.html
  4. 43
      apps/course/templates/course/course_only_lessons.html
  5. 7
      apps/course/views.py
  6. 13
      apps/payment/models.py
  7. 6
      apps/payment/views.py
  8. 4
      apps/user/templates/user/payment-history.html
  9. 3
      apps/user/views.py
  10. 2
      docker-compose.yml
  11. 25
      project/settings.py
  12. 19
      project/templates/lilcity/index.html
  13. 2
      project/templates/lilcity/main.html
  14. 2
      project/urls.py
  15. 1
      requirements.txt
  16. 12191
      web/package-lock.json
  17. 6
      web/src/js/modules/popup.js
  18. 1
      web/src/sass/_common.sass
  19. 4
      web/yarn.lock

@ -1 +1,2 @@
venv*/
web/node_modules/

@ -13,11 +13,12 @@ def _set_constance_value(key, value):
class ConfigSerializer(serializers.Serializer):
SERVICE_COMMISSION = serializers.IntegerField(required=False)
SERVICE_DISCOUNT_MIN_AMOUNT = serializers.IntegerField(required=False)
SERVICE_DISCOUNT = serializers.IntegerField(required=False)
INSTAGRAM_CLIENT_ACCESS_TOKEN = serializers.CharField(required=False)
INSTAGRAM_CLIENT_SECRET = serializers.CharField(required=False)
INSTAGRAM_RESULTS_TAG = serializers.CharField(required=False)
INSTAGRAM_RESULTS_PATH = serializers.CharField(required=False)
SERVICE_COMMISSION = serializers.IntegerField(required=False)
INSTAGRAM_PROFILE_URL = serializers.CharField(required=False)
def to_representation(self, instance):
ret = OrderedDict()

@ -25,7 +25,7 @@
</div>
<div class="go__title">Вернуться</div>
</a>
{% if not paid and course.price %}
{% if course.author != request.user and not paid and course.price %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
@ -138,23 +138,30 @@
</div>
<div class="course__actions">
<a href="{% url 'course' course.id %}" class="course__action btn btn_lg btn_stroke">Описание курса</a>
{% if request.user.role == request.user.AUTHOR_ROLE or request.user.role == request.user.ADMIN_ROLE %}
<a href="{% url 'course-only-lessons' course.id %}"
class="course__action btn btn_lg btn_gray"
{% if not user.is_authenticated %}data-popup=".js-popup-auth"{% endif %}
>УРОКИ
{% comment %} <svg class="icon icon-lock">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use>
</svg> {% endcomment %}
{% if request.user.is_authenticated %}
{% if cource.author == request.user and request.user.role == request.user.AUTHOR_ROLE or request.user.role == request.user.ADMIN_ROLE %}
<a
href="{% url 'course-only-lessons' course.id %}"
class="course__action btn btn_lg btn_gray"
{% if not user.is_authenticated %}data-popup=".js-popup-auth"{% endif %}
>УРОКИ
</a>
{% else %}
<button
class="course__action btn btn_lg btn_gray"
>УРОКИ
<svg class="icon icon-lock">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use>
</svg>
</button>
<a
class="course__action btn btn_lg btn_gray"
{% if paid %}
href="{% url 'course-only-lessons' course.id %}"
{% else %}
data-popup=".js-popup-course-lock"
{% endif %}
>УРОКИ
{% if not paid %}
<svg class="icon icon-lock">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use>
</svg>
{% endif %}
</a>
{% endif %}
{% endif %}
</div>
<a class="course__video video" href="#">
@ -237,8 +244,8 @@
{% with template="course/content/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}
</div>
</div>
</div>
{% endfor %}
{% if user.is_authenticated and course.lessons.exists %}
<div class="section">
@ -255,22 +262,13 @@
<div class="lessons__item">
<div class="lessons__subtitle subtitle">{{ lesson.title }}</div>
<div class="lessons__row">
{% comment %} {% if lesson.cover %}
<div class="lessons__preview">
<img class="lessons__pic" src="{{ lesson.cover.url }}">
</div>
{% else %}
<div class="lessons__preview">
<img class="lessons__pic" src="{% static 'img/pic-3.jpg' %}">
</div>
{% endif %} {% endcomment %}
<div class="lessons__content">{{ lesson.short_description | safe }}</div>
</div>
</div>
{% if request.user.role == request.user.AUTHOR_ROLE or request.user.role == request.user.ADMIN_ROLE %}
<a href="{% url 'lesson' pk=lesson.id %}">
</a>
{% else %}
<div>
</div>
{% endif %}
{% endfor %}
</div>
@ -391,7 +389,7 @@
</div>
{% endif %}
</div>
{% if not paid and course.price %}
{% if course.author != request.user and not paid and course.price %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}

@ -25,14 +25,18 @@
</div>
<div class="go__title">Вернуться</div>
</a>
<button
class="go__btn btn btn_md"
{% if not paid and course.price %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
data-popup=".js-popup-buy"
{% if not pending %}
href="{% url 'course-checkout' course.id %}"
{% endif %}
{% else %}
data-popup=".js-popup-auth"
{% endif %}
>КУПИТЬ КУРС</button>
>{% if pending %}ОЖИДАЕТСЯ ПОДТВЕРЖДЕНИЕ ОПЛАТЫ{% else %}КУПИТЬ КУРС{% endif %}</a>
{% endif %}
</div>
<div
class="course"
@ -130,15 +134,26 @@
</div>
</div>
<div class="course__actions">
<a href="{% url 'course' course.id %}" class="course__action btn btn_lg btn_gray">Описание курса</a>
<a href="{% url 'course-only-lessons' course.id %}"
class="course__action btn btn_lg btn_stroke"
{% if not user.is_authenticated %}data-popup=".js-popup-auth"{% endif %}
>УРОКИ
{% comment %} <svg class="icon icon-lock">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use>
</svg> {% endcomment %}
</a>
<a href="{% url 'course' course.id %}" class="course__action btn btn_lg btn_stroke">Описание курса</a>
{% if request.user.role == request.user.AUTHOR_ROLE or request.user.role == request.user.ADMIN_ROLE %}
<a
href="{% url 'course-only-lessons' course.id %}"
class="course__action btn btn_lg btn_gray"
{% if not user.is_authenticated %}data-popup=".js-popup-auth"{% endif %}
>УРОКИ
</a>
{% else %}
<a
href="{% if paid %}{% url 'course-only-lessons' course.id %}{% else %}#{% endif %}"
class="course__action btn btn_lg btn_gray"
>УРОКИ
{% if not paid %}
<svg class="icon icon-lock">
<use xlink:href="{% static 'img/sprite.svg' %}#icon-lock"></use>
</svg>
{% endif %}
</a>
{% endif %}
</div>
</div>
</div>
@ -270,4 +285,4 @@
</div>
</div>
</div>
{% endblock content %}
{% endblock content %}

@ -164,6 +164,8 @@ class CourseEditView(TemplateView):
self.object = drafts.last()
else:
self.object = Course.objects.create()
if request.user != self.object.author and request.user.role not in [User.ADMIN_ROLE, User.AUTHOR_ROLE]:
raise Http404
return super().get(request)
def get_context_data(self):
@ -178,10 +180,13 @@ class CourseView(DetailView):
model = Course
context_object_name = 'course'
template_name = 'course/course.html'
only_lessons = False
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
if (not request.user.is_authenticated and self.object.status != Course.PUBLISHED):
context = self.get_context_data()
if not request.user.is_authenticated or (not request.user.is_authenticated and self.object.status != Course.PUBLISHED) or\
(request.user.is_authenticated and request.user.role not in [User.AUTHOR_ROLE, User.ADMIN_ROLE] and self.object.author != request.user and self.only_lessons and not context['paid']):
raise Http404
return response

@ -144,7 +144,10 @@ class SchoolPayment(Payment):
verbose_name_plural = 'Платежи за школу'
def __str__(self):
days = ', '.join([dict(SchoolSchedule.WEEKDAY_CHOICES).get(weekday, '') for weekday in sorted(self.weekdays)])
days = ', '.join([
dict(SchoolSchedule.WEEKDAY_CHOICES).get(weekday, '')
for weekday in sorted(self.weekdays)
])
return days
def save(self, *args, **kwargs):
@ -152,9 +155,11 @@ class SchoolPayment(Payment):
weekday__in=self.weekdays,
).aggregate(
models.Sum('month_price'),
models.Sum('day_discount'),
)
month_price_sum = aggregate.get('month_price__sum', 0)
day_discount_sum = aggregate.get('day_discount__sum', 0) if len(self.weekdays) == 7 else 0
self.amount = month_price_sum - day_discount_sum
if len(self.weekdays) > 7:
discount = config.SERVICE_DISCOUNT
else:
discount = 0
self.amount = month_price_sum - discount
super().save(*args, **kwargs)

@ -4,6 +4,7 @@ import json
from datetime import timedelta
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import redirect
from django.views.generic import View, TemplateView
@ -22,12 +23,16 @@ from .models import AuthorBalance, CoursePayment, SchoolPayment
logger = logging.getLogger('django')
@method_decorator(login_required, name='dispatch')
class CourseBuyView(TemplateView):
template_name = 'payment/paymentwall_widget.html'
def get(self, request, pk=None, *args, **kwargs):
host = request.scheme + '://' + request.get_host()
course = Course.objects.get(id=pk)
if request.user == course.author:
messages.error('Вы не можете приобрести свой курс.')
return redirect(reverse_lazy('course', args=[course.id]))
course_payment = CoursePayment.objects.create(
user=request.user,
course=course,
@ -54,6 +59,7 @@ class CourseBuyView(TemplateView):
return self.render_to_response(context={'widget': widget.get_html_code()})
@method_decorator(login_required, name='dispatch')
class SchoolBuyView(TemplateView):
template_name = 'payment/paymentwall_widget.html'

@ -105,7 +105,7 @@
{% else %}
<div class="transactions__cell">
{% if payment.is_deliverable %}
<span class="transactions__cell__success">Получено</span>
<span class="transactions__cell__success">Оплачено</span>
{% elif payment.is_under_review %}
<span class="transactions__cell__pending">Ожидается подтверждение оплаты</span>
{% else %}
@ -132,4 +132,4 @@
<script>
(new Inputmask('9999 9999 9999 9999', {placeholder: "0"})).mask(document.querySelector('#id_card'));
</script>
{% endblock foot %}
{% endblock foot %}

@ -6,6 +6,7 @@ from os.path import splitext
from datetime import timedelta
from paymentwall import Pingback
from django.conf import settings
from django.contrib.auth import login
from django.core.exceptions import ValidationError
from django.shortcuts import render, reverse, redirect
@ -102,7 +103,7 @@ class PaymentHistoryView(FormView):
def post(self, request, pk=None):
form = self.get_form()
if AuthorBalance.objects.filter(created_at__gte=now() - timedelta(days=30)).exists():
if not settings.DEBUG and AuthorBalance.objects.filter(created_at__gte=now() - timedelta(days=30)).exists():
messages.error(request, 'Запрос на вывод средств можно сделать только один раз в 30 дней.')
return self.form_invalid(form)
if form.is_valid():

@ -22,7 +22,7 @@ services:
restart: always
volumes:
- .:/lilcity
command: bash -c "python manage.py migrate && python manage.py loaddata /lilcity/apps/*/fixtures/*.json && python manage.py runserver 0.0.0.0:8000 && celery worker -A project"
command: bash -c "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"
environment:
- DJANGO_SETTINGS_MODULE=project.settings
- DATABASE_SERVICE_HOST=db

@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
"""
import os
from celery.schedules import crontab
from collections import OrderedDict
from datetime import timedelta
@ -87,6 +88,7 @@ TEMPLATES = [
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'constance.context_processors.config',
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
@ -219,7 +221,7 @@ CELERY_TASK_SERIALIZER = 'json'
CELERY_BEAT_SCHEDULE = {
'retrieve_photos_from_instagram': {
'task': 'apps.content.tasks.retrieve_photos',
'schedule': timedelta(minutes=2),
'schedule': timedelta(minutes=2) if DEBUG else crontab(minute=0, hour=0),
'args': (),
},
}
@ -230,11 +232,30 @@ CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
CONSTANCE_CONFIG = OrderedDict((
('INSTAGRAM_CLIENT_ACCESS_TOKEN', ('7145314808.f6fa114.ce354a5d876041fc9d3db04b0045587d', '')),
('INSTAGRAM_CLIENT_SECRET', ('2334a921425140ccb180d145dcd35b25', '')),
('INSTAGRAM_PROFILE_URL', ('#', 'URL профиля Instagram.')),
('INSTAGRAM_RESULTS_TAG', ('#lil_акварель', 'Тэг результатов работ.')),
('INSTAGRAM_RESULTS_PATH', ('media/instagram/results/', 'Путь до результатов работ.')),
('SERVICE_COMMISSION', (10, 'Комиссия сервиса в процентах.'))
('SERVICE_COMMISSION', (10, 'Комиссия сервиса в процентах.')),
('SERVICE_DISCOUNT_MIN_AMOUNT', (3500, 'Минимальная сумма платежа для школы, после которой вычитывается скидка SERVICE_DISCOUNT.')),
('SERVICE_DISCOUNT', (1000, 'Комиссия сервиса при покупке всех дней.')),
))
CONSTANCE_CONFIG_FIELDSETS = OrderedDict({
'Service': (
'SERVICE_COMMISSION',
'SERVICE_DISCOUNT_MIN_AMOUNT',
'SERVICE_DISCOUNT',
),
'Instagram': (
'INSTAGRAM_CLIENT_ACCESS_TOKEN',
'INSTAGRAM_CLIENT_SECRET',
'INSTAGRAM_PROFILE_URL',
'INSTAGRAM_RESULTS_TAG',
'INSTAGRAM_RESULTS_PATH',
),
})
try:
from .local_settings import *
except ImportError:

@ -440,7 +440,7 @@
<div class="buy__list">
{% for school_schedule in school_schedules %}
<label class="switch switch_lesson">
<input class="switch__input" type="checkbox" data-day="{{school_schedule.weekday}}" data-price="{{school_schedule.month_price}}" data-discount="{{school_schedule.day_discount}}" autocomplete="off">
<input class="switch__input" type="checkbox" data-day="{{school_schedule.weekday}}" data-price="{{school_schedule.month_price}}" autocomplete="off">
<span class="switch__content">
<span class="switch__cell">{{ school_schedule }}</span>
{% comment %} dont delete {% endcomment %}
@ -477,8 +477,25 @@
</div>
</div>
</div>
{% if course %}
<div class="popup js-popup-course-lock">
<div class="popup__wrap popup__wrap_md js-popup-wrap">
<div class="popup__body">
<div class="lock">
<div class="lock__label">ЧТОБЫ ПРОДОЛЖИТЬ ПРОСМОТР</div>
<div class="lock__title title">Как просто научиться рисовать простых персонажей.</div>
<div class="lock__content">Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.</div>
<a href="{% url 'course-checkout' course.id %}" class="lock__btn btn">КУПИТЬ КУРС</a>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<script type="text/javascript" src={% static "app.js" %}></script>
<script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});
</script>
{% block foot %}{% endblock foot %}
</body>

@ -190,7 +190,7 @@
<div class="title title_center">Галерея</div></a>
<div class="text">
<p>Тысячи шедевров уже созданы благодаря Lil City School. Более 10000 работ можно
<a href='#'>увидеть</a> в Инстаграм</p>
<a href='{{ config.INSTAGRAM_PROFILE_URL }}'>увидеть</a> в Инстаграм</p>
<img class="text__curve text__curve_three" src="{% static 'img/curve-3.svg' %}">
</div>
<div class="gallery">

@ -42,7 +42,7 @@ urlpatterns = [
path('course/<str:slug>/', CourseView.as_view(), name='course'),
path('course/<int:pk>/checkout', CourseBuyView.as_view(), name='course-checkout'),
path('course/<int:pk>/edit', CourseEditView.as_view(), name='course_edit'),
path('course/<int:pk>/lessons', CourseView.as_view(template_name='course/course_only_lessons.html'), name='course-only-lessons'),
path('course/<int:pk>/lessons', CourseView.as_view(template_name='course/course_only_lessons.html', only_lessons=True), name='course-only-lessons'),
path('course/<int:course_id>/like', likes, name='likes'),
path('course/<int:course_id>/comment', coursecomment, name='coursecomment'),
path('lesson/<int:pk>/', LessonView.as_view(), name='lesson'),

@ -1,4 +1,5 @@
# Python-3.6
gunicorn==19.7.1
requests==2.18.4
Django==2.0.2
django-anymail[mailgun]==1.2

12191
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -52,10 +52,9 @@ $(document).ready(function () {
$(document).on('change', '[data-day]', function(){
var weekday = $(this).data('day');
var price = $(this).data('price');
var discount = $(this).data('discount');
if($(this).is(':checked')) {
console.log('checked');
selectedWeekdays[weekday] = {price:price, discount:discount};
selectedWeekdays[weekday] = {price:price};
} else {
console.log('not checked');
delete selectedWeekdays[weekday];
@ -69,14 +68,13 @@ $(document).ready(function () {
var weekdays = [], daysText = [], price = 0, discount = 0;
for(var i in selectedWeekdays) {
price += parseInt(selectedWeekdays[i].price);
discount += parseInt(selectedWeekdays[i].discount);
weekdays.push(i);
daysText.push(days[i]);
}
var text = '';
if(weekdays.length >= 7) {
text = '<del>'+price+'</del> '+(price-discount)+'р.';
text = '<del>'+price+'</del> '+(price-schoolDiscount)+'р.';
} else {
text = price+'p.';
}

@ -3352,7 +3352,6 @@ a
+m
flex: 0 0 calc(100% - 20px)
&__list
display: flex
margin: 0 -10px
flex-wrap: wrap
+m

@ -3339,6 +3339,10 @@ ini@^1.3.4, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
inputmask@^3.3.11:
version "3.3.11"
resolved "https://registry.yarnpkg.com/inputmask/-/inputmask-3.3.11.tgz#1421c94ae28c3dcd1b4d26337b508bb34998e2d8"
interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"

Loading…
Cancel
Save