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

remotes/origin/hasaccess
Vitaly Baev 8 years ago
commit f758b91392
  1. 38
      api/v1/serializers/config.py
  2. 29
      api/v1/serializers/payment.py
  3. 1
      api/v1/serializers/school.py
  4. 7
      api/v1/urls.py
  5. 31
      api/v1/views.py
  6. 36
      apps/content/tasks.py
  7. 730
      apps/course/fixtures/course.json
  8. 8
      apps/course/templates/course/course.html
  9. 68
      apps/course/templates/course/school.html
  10. 5
      apps/course/templatetags/plural.py
  11. 25
      apps/course/views.py
  12. 8
      apps/notification/templates/notification/email/decline_withdrawal.html
  13. 18
      apps/payment/migrations/0011_authorbalance_cause.py
  14. 18
      apps/payment/migrations/0012_auto_20180302_0740.py
  15. 36
      apps/payment/migrations/0013_auto_20180304_1757.py
  16. 29
      apps/payment/migrations/0014_auto_20180307_0617.py
  17. 18
      apps/payment/migrations/0015_auto_20180307_0911.py
  18. 62
      apps/payment/models.py
  19. 43
      apps/payment/views.py
  20. 93
      apps/school/fixtures/school_schedules.json
  21. 25
      apps/user/fields.py
  22. 66
      apps/user/fixtures/superuser.json
  23. 7
      apps/user/forms.py
  24. 102
      apps/user/templates/user/payment-history.html
  25. 4
      apps/user/templates/user/profile.html
  26. 70
      apps/user/views.py
  27. 0
      media/instagram/results/.gitkeep
  28. 12
      project/settings.py
  29. 6
      project/templates/lilcity/index.html
  30. 28
      project/templates/lilcity/main.html
  31. 15
      project/urls.py
  32. 16
      project/views.py
  33. 4
      requirements.txt
  34. 5
      web/package-lock.json
  35. 1
      web/package.json
  36. 3
      web/src/js/modules/common.js
  37. 43
      web/src/js/modules/popup.js
  38. 20
      web/src/sass/_common.sass
  39. 27
      web/yarn.lock

@ -0,0 +1,38 @@
from constance import config
from constance.admin import get_values, ConstanceForm
from rest_framework import serializers
from rest_framework.fields import SkipField
from collections import OrderedDict
def _set_constance_value(key, value):
form = ConstanceForm(initial=get_values())
field = form.fields[key]
clean_value = field.clean(field.to_python(value))
setattr(config, key, clean_value)
class ConfigSerializer(serializers.Serializer):
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)
def to_representation(self, instance):
ret = OrderedDict()
fields = self._readable_fields
for field in fields:
attribute = instance.get(field.field_name)
ret[field.field_name] = field.to_representation(attribute)
return ret
def to_internal_value(self, data):
ret = OrderedDict(get_values())
for k, v in data.items():
ret[k] = v
return ret
def update(self, instance, validated_data):
for k, v in validated_data.items():
_set_constance_value(k, v)

@ -1,9 +1,37 @@
from rest_framework import serializers
from apps.payment.models import AuthorBalance
from .user import UserSerializer
class AuthorBalanceCreateSerializer(serializers.ModelSerializer):
class Meta:
model = AuthorBalance
fields = (
'id',
'author',
'type',
'amount',
'commission',
'status',
'payment',
'cause',
)
read_only_fields = (
'id',
'author',
'type',
'payment',
)
def to_representation(self, instance):
return AuthorBalanceSerializer(instance, context=self.context).to_representation(instance)
class AuthorBalanceSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = AuthorBalance
@ -15,6 +43,7 @@ class AuthorBalanceSerializer(serializers.ModelSerializer):
'commission',
'status',
'payment',
'cause',
)
read_only_fields = (

@ -15,6 +15,7 @@ class SchoolScheduleSerializer(serializers.ModelSerializer):
'materials',
'age',
'month_price',
'day_discount',
)
read_only_fields = (

@ -8,7 +8,7 @@ from drf_yasg import openapi
from .auth import ObtainToken
from .views import (
AuthorBalanceViewSet,
AuthorBalanceViewSet, ConfigViewSet,
CategoryViewSet, CourseViewSet,
MaterialViewSet, LikeViewSet,
ImageViewSet, TextViewSet,
@ -38,6 +38,8 @@ router.register(r'school-schedules', SchoolScheduleViewSet, base_name='school-sc
router.register(r'users', UserViewSet, base_name='users')
# router.register(r'configs', ConfigViewSet, base_name='configs')
schema_view = get_schema_view(
openapi.Info(
@ -51,9 +53,10 @@ schema_view = get_schema_view(
)
urlpatterns = [
path('api-token-auth/', ObtainToken.as_view(), name='api-token-auth'),
path('configs/', ConfigViewSet.as_view(), name='configs'),
path('swagger(<str:format>.json|.yaml)', schema_view.without_ui(cache_timeout=None), name='schema-json'),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
path('api-token-auth/', ObtainToken.as_view(), name='api-token-auth'),
path('', include((router.urls, 'api-root')), name='api-root'),
]

@ -1,12 +1,16 @@
from constance.admin import get_values
from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework import viewsets
from rest_framework import views, viewsets
from rest_framework import generics
from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from . import ExtendedModelViewSet
from .serializers.config import ConfigSerializer
from .serializers.course import (
CategorySerializer, LikeSerializer,
CourseSerializer, CourseCreateSerializer,
@ -24,7 +28,7 @@ from .serializers.content import (
ImageObjectSerializer,
)
from .serializers.school import SchoolScheduleSerializer
from .serializers.payment import AuthorBalanceSerializer
from .serializers.payment import AuthorBalanceSerializer, AuthorBalanceCreateSerializer
from .serializers.user import (
UserSerializer, UserPhotoSerializer,
)
@ -38,14 +42,18 @@ from apps.content.models import (
)
from apps.payment.models import AuthorBalance
from apps.school.models import SchoolSchedule
User = get_user_model()
class AuthorBalanceViewSet(ExtendedModelViewSet):
queryset = AuthorBalance.objects.all()
serializer_class = AuthorBalanceSerializer
serializer_class = AuthorBalanceCreateSerializer
serializer_class_map = {
'list': AuthorBalanceSerializer,
}
permission_classes = (IsAdmin,)
filter_fields = ('status', 'type')
filter_fields = ('author', 'status', 'type')
search_fields = (
'author__email',
'author__first_name',
@ -306,3 +314,18 @@ class SchoolScheduleViewSet(ExtendedModelViewSet):
queryset = SchoolSchedule.objects.all()
serializer_class = SchoolScheduleSerializer
permission_classes = (IsAdmin,)
class ConfigViewSet(generics.RetrieveUpdateAPIView):
serializer_class = ConfigSerializer
permission_classes = (IsAdmin,)
def retrieve(self, request, *args, **kwargs):
serializer = ConfigSerializer(get_values())
return Response(serializer.data)
def patch(self, request, *args, **kwargs):
serializer = ConfigSerializer(data=request.data)
if serializer.is_valid():
serializer.update(get_values(), serializer.validated_data)
return Response(serializer.data)

@ -1,19 +1,31 @@
import os
import json
from time import sleep
from project.celery import app
import requests
import shutil
from constance import config
from InstagramAPI import InstagramAPI
from instagram.client import InstagramAPI
from project.celery import app
from time import sleep
from django.conf import settings
@app.task
def retrieve_photos():
instagram = InstagramAPI(
config.INSTAGRAM_CLIENT_LOGIN,
config.INSTAGRAM_CLIENT_PASSWORD,
api = InstagramAPI(
access_token=config.INSTAGRAM_CLIENT_ACCESS_TOKEN,
client_secret=config.INSTAGRAM_CLIENT_SECRET,
)
instagram.login()
sleep(1)
if instagram.isLoggedIn and instagram.getHashtagFeed(config.INSTAGRAM_RESULTS_TAG):
with open('s.json', 'w') as f:
f.write(json.dumps(instagram.LastJson))
return instagram.LastJson
recent_media, next_ = api.user_recent_media(user_id='self', count=20)
path = os.path.join(settings.BASE_DIR, config.INSTAGRAM_RESULTS_PATH)
for idx, media in enumerate(recent_media):
try:
fname = os.path.join(path, f'{idx}.jpg')
r = requests.get(media.images['standard_resolution'].url, stream=True)
if r.status_code == 200:
with open(fname, 'wb') as f:
r.raw.decode_content = True
shutil.copyfileobj(r.raw, f)
except AttributeError:
pass

@ -1,367 +1,367 @@
[
{
"model": "course.course",
"pk": 1,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "1500.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 2,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:04:41.113Z",
"update_at": "2018-01-31T15:03:47.118Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 2,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "1900.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 1,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.437Z",
"update_at": "2018-01-31T15:03:47.115Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 3,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 9,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.442Z",
"update_at": "2018-01-31T15:03:47.112Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 4,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "400.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 8,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.445Z",
"update_at": "2018-01-31T15:03:47.108Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 5,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "1800.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 7,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.449Z",
"update_at": "2018-01-31T15:03:47.104Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 6,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 6,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.452Z",
"update_at": "2018-01-31T15:03:47.101Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 7,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "1600.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 5,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.455Z",
"update_at": "2018-01-31T15:03:47.097Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 8,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "1900.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 4,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.458Z",
"update_at": "2018-01-31T15:03:47.093Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 9,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "200.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 3,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.461Z",
"update_at": "2018-01-31T15:03:47.089Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 10,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "800.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 2,
"duration": 1,
"is_featured": true,
"status": 1,
"created_at": "2018-01-27T07:09:03.464Z",
"update_at": "2018-01-31T15:03:47.086Z",
"likes": [],
"materials": [
1,
2,
3
]
}
},
{
"model": "course.course",
"pk": 11,
"fields": {
"author": 1,
"title": "\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 \u0434\u043b\u044f \u0434\u0435\u0442\u0435\u0439 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043c \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438",
"short_description": "\u042d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u0442\u044f\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u0442\u043e\u043c \u043a\u0430\u043a \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0444\u043e\u0440\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0435\u043b\u044b\u0439 \u0438 \u0445\u0430\u0440\u0438\u0437\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u0439.",
"from_author": "",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": "2018-02-28T12:00:00Z",
"category": 1,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.467Z",
"update_at": "2018-01-31T15:03:47.080Z",
"likes": [],
"materials": [
1,
2,
3
]
}
},
{
"model": "course.category",
"pk": 1,
"fields": {
"title": "\u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436"
}
},
{
"model": "course.category",
"pk": 2,
"fields": {
"title": "\u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c"
}
},
{
"model": "course.category",
"pk": 3,
"fields": {
"title": "\u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u044f"
}
},
{
"model": "course.category",
"pk": 4,
"fields": {
"title": "\u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f"
}
},
{
"model": "course.category",
"pk": 5,
"fields": {
"title": "\u043f\u0430\u0441\u0442\u0435\u043b\u044c"
}
},
{
"model": "course.category",
"pk": 6,
"fields": {
"title": "\u043f\u043b\u0430\u0441\u0442\u0438\u043b\u0438\u043d"
}
},
{
"model": "course.category",
"pk": 7,
"fields": {
"title": "\u043a\u0440\u0435\u0430\u0442\u0438\u0432\u043d\u043e\u0435 \u043c\u044b\u0448\u043b\u0435\u043d\u0438\u0435"
}
},
{
"model": "course.category",
"pk": 8,
"fields": {
"title": "\u043c\u043e\u0442\u043e\u0440\u0438\u043a\u0430"
}
},
{
"model": "course.category",
"pk": 9,
"fields": {
"title": "\u0436\u0438\u0432\u043e\u043f\u0438\u0441\u044c"
}
},
{
"model": "course.lesson",
"pk": 1,
"fields": {
"title": "1 \u0423\u0420\u041e\u041a",
"short_description": "\u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0441\u044e\u0436\u0435\u0442, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043d\u0430 \u043a\u0443\u0440\u0441\u0435 \u0438 \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u0433\u0435\u0440\u043e\u044f \u0432\u0430\u0448\u0435\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0438. \u0421 \u044d\u0442\u0438\u043c \u0433\u0435\u0440\u043e\u0435\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u0438\u0438 \u0432\u0441\u0435\u0433\u043e \u043a\u0443\u0440\u0441\u0430.",
"course": 11,
"created_at": "2018-01-31T15:06:14.830Z",
"update_at": "2018-01-31T15:06:14.830Z"
}
},
{
"model": "course.lesson",
"pk": 2,
"fields": {
"title": "2 \u0423\u0420\u041e\u041a",
"short_description": "\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u0438 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u043c \u0441 \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0433\u0435\u0440\u043e\u044f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0435\u0439 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430. \u041f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u0440\u0438\u0437\u043d\u0430\u043a\u0443 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u00ab\u0440\u0435\u0444\u0435\u0440\u0435\u043d\u0441\u044b\u00bb. \u0420\u0438\u0441\u0443\u0435\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043e\u0431\u0440\u0430\u0437\u0430 \u0432 \u0441\u0432\u043e\u0435\u043c \u0441\u0442\u0438\u043b\u0435.\r\n\r\n\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0438\u0437 \u043d\u0438\u0445 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430. \u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0441\u0430\u043c\u044b\u0435 \u0443\u0434\u0430\u0447\u043d\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u043c \u0438\u0445 \u0432 \u043e\u0434\u043d\u043e\u043c \u043d\u0430\u0431\u0440\u043e\u0441\u043a\u0435.",
"course": 11,
"created_at": "2018-01-31T15:06:46.772Z",
"update_at": "2018-01-31T15:06:46.772Z"
}
},
{
"model": "course.lesson",
"pk": 3,
"fields": {
"title": "3 \u0423\u0420\u041e\u041a",
"short_description": "\u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043e\u0436\u0438\u0432\u0438\u0442\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430. \u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c 5 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u0432\u0430\u0448\u0435\u0433\u043e \u0433\u0435\u0440\u043e\u044f \u0438 \u0442\u043e\u0433\u043e \u0441\u044e\u0436\u0435\u0442\u0430, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u043d \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0443\u0447\u0430\u0441\u0442\u0438\u0435, \u0440\u0438\u0441\u0443\u0435\u043c \u044d\u0441\u043a\u0438\u0437\u044b \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445 \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u043f\u043e\u0437\u0430\u0445 \u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438.\r\n\r\n\u0412\u044b\u0434\u0435\u043b\u044f\u0435\u043c 5 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445 \u044d\u043c\u043e\u0446\u0438\u0439 \u0434\u043b\u044f \u0433\u0435\u0440\u043e\u044f, \u043d\u0430\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c. \u041f\u043e \u044d\u043c\u043e\u0446\u0438\u044f\u043c \u043f\u043e\u0434\u0431\u0438\u0440\u0430\u0435\u043c \u0440\u0435\u0444\u0435\u0440\u0435\u043d\u0441\u044b \u0438 \u0441\u0442\u0438\u043b\u0438\u0437\u0443\u0435\u043c \u0438\u0445 \u0432 \u0441\u0432\u043e\u0435\u043c \u0441\u0442\u0438\u043b\u0435.",
"course": 11,
"created_at": "2018-01-31T15:07:08.979Z",
"update_at": "2018-01-31T15:07:08.979Z"
}
},
{
"model": "course.material",
"pk": 1,
"fields": {
"title": "\u0411\u0443\u043c\u0430\u0433\u0430 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u043d\u0430\u044f",
"short_description": "\u0411\u0443\u043c\u0430\u0433\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u044e \u0438\u043c\u0435\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u042d\u0442\u043e \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u0435\u043c, \u0447\u0442\u043e \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c \u2014 \u043a\u0440\u0430\u0441\u043a\u0430 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u0430\u044f, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442 \u0444\u0430\u043a\u0442\u0443\u0440\u0430 \u0431\u0443\u043c\u0430\u0433\u0438 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0432\u0438\u0434 \u043a\u0440\u0430\u0441\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f.",
"created_at": "2018-01-31T14:55:48.394Z",
"update_at": "2018-01-31T14:55:48.394Z"
}
},
{
"model": "course.material",
"pk": 2,
"fields": {
"title": "\u041a\u0438\u0441\u0442\u043e\u0447\u043a\u0438 \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f",
"short_description": "\u041a\u0438\u0441\u0442\u044c \u2014 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u043f\u043e\u043a\u0440\u0430\u0441\u043a\u0438 \u0438 \u0436\u0438\u0432\u043e\u043f\u0438\u0441\u0438. \u041a\u0438\u0441\u0442\u0438 \u0434\u0435\u043b\u0430\u044e\u0442\u0441\u044f \u0438\u0437 \u0449\u0435\u0442\u0438\u043d\u044b \u0438 \u0445\u0432\u043e\u0441\u0442\u043e\u0432\u044b\u0445 \u0432\u043e\u043b\u043e\u0441\u043a\u043e\u0432 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0436\u0438\u0432\u043e\u0442\u043d\u044b\u0445.",
"created_at": "2018-01-31T14:57:37.751Z",
"update_at": "2018-01-31T14:57:37.751Z"
}
},
{
"model": "course.material",
"pk": 3,
"fields": {
"title": "\u041a\u0440\u0430\u0441\u043a\u0438 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u043d\u044b\u0435",
"short_description": "\u0417\u0430\u0432\u043e\u0434 \u0445\u0443\u0434\u043e\u0436\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043a\u0440\u0430\u0441\u043e\u043a \u00ab\u041d\u0435\u0432\u0441\u043a\u0430\u044f \u043f\u0430\u043b\u0438\u0442\u0440\u0430\u00bb \u0432\u044b\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c 80 \u043b\u0435\u0442, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044f \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0438\u0438 \u0432\u044b\u0441\u043e\u0447\u0430\u0439\u0448\u0435\u0433\u043e \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u0440\u0435\u0446\u0435\u043f\u0442\u0443\u0440\u044b, \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u0430\u044f \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u0438\u0437\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0430\u043a\u0432\u0430\u0440\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u0440\u0430\u0441\u043a\u0438 \u0432\u0438\u0437\u0438\u0442\u043d\u043e\u0439 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u043e\u0439 \u043f\u0440\u0435\u0434\u043f\u0440\u0438\u044f\u0442\u0438\u044f \u0432 \u0420\u043e\u0441\u0441\u0438\u0438",
"created_at": "2018-01-31T14:58:46.209Z",
"update_at": "2018-01-31T14:58:46.209Z"
}
{
"model": "course.course",
"pk": 1,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "1500.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 2,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:04:41.113Z",
"update_at": "2018-01-31T15:03:47.118Z",
"likes": [],
"materials": []
}
]
},
{
"model": "course.course",
"pk": 2,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "1900.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 1,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.437Z",
"update_at": "2018-01-31T15:03:47.115Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 3,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 9,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.442Z",
"update_at": "2018-01-31T15:03:47.112Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 4,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "400.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 8,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.445Z",
"update_at": "2018-01-31T15:03:47.108Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 5,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "1800.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 7,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.449Z",
"update_at": "2018-01-31T15:03:47.104Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 6,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 6,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.452Z",
"update_at": "2018-01-31T15:03:47.101Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 7,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "1600.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 5,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.455Z",
"update_at": "2018-01-31T15:03:47.097Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 8,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "1900.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 4,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.458Z",
"update_at": "2018-01-31T15:03:47.093Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 9,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "200.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 3,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.461Z",
"update_at": "2018-01-31T15:03:47.089Z",
"likes": [],
"materials": []
}
},
{
"model": "course.course",
"pk": 10,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "800.00",
"is_infinite": false,
"deferred_start_at": null,
"category": 2,
"duration": 1,
"is_featured": true,
"status": 1,
"created_at": "2018-01-27T07:09:03.464Z",
"update_at": "2018-01-31T15:03:47.086Z",
"likes": [],
"materials": [
1,
2,
3
]
}
},
{
"model": "course.course",
"pk": 11,
"fields": {
"author": 1,
"title": "Базовый курс для детей по основам иллюстрации",
"short_description": "Этот курс поможет детям узнать о том как из простых форм создавать веселый и харизматичных персонажей.",
"from_author": "",
"price": "100.00",
"is_infinite": false,
"deferred_start_at": "2018-02-28T12:00:00Z",
"category": 1,
"duration": 1,
"is_featured": false,
"status": 1,
"created_at": "2018-01-27T07:09:03.467Z",
"update_at": "2018-01-31T15:03:47.080Z",
"likes": [],
"materials": [
1,
2,
3
]
}
},
{
"model": "course.category",
"pk": 1,
"fields": {
"title": "персонаж"
}
},
{
"model": "course.category",
"pk": 2,
"fields": {
"title": "акварель"
}
},
{
"model": "course.category",
"pk": 3,
"fields": {
"title": "иллюстрация"
}
},
{
"model": "course.category",
"pk": 4,
"fields": {
"title": "анимация"
}
},
{
"model": "course.category",
"pk": 5,
"fields": {
"title": "пастель"
}
},
{
"model": "course.category",
"pk": 6,
"fields": {
"title": "пластилин"
}
},
{
"model": "course.category",
"pk": 7,
"fields": {
"title": "креативное мышление"
}
},
{
"model": "course.category",
"pk": 8,
"fields": {
"title": "моторика"
}
},
{
"model": "course.category",
"pk": 9,
"fields": {
"title": "живопись"
}
},
{
"model": "course.lesson",
"pk": 1,
"fields": {
"title": "1 УРОК",
"short_description": "Выбираем сюжет, с которым мы будем работать на курсе и главного героя вашей истории. С этим героем мы будем работать на протяжении всего курса.",
"course": 11,
"created_at": "2018-01-31T15:06:14.830Z",
"update_at": "2018-01-31T15:06:14.830Z"
}
},
{
"model": "course.lesson",
"pk": 2,
"fields": {
"title": "2 УРОК",
"short_description": "Собираем материал и экспериментируем с образом героя, используя полученный список характерных особенностей персонажа. По каждому признаку нужно будет собрать «референсы». Рисуем элементы образа в своем стиле.\\r\\n\\r\\nСобираем из них нашего персонажа. Выбираем самые удачные элементы, объединяем их в одном наброске.",
"course": 11,
"created_at": "2018-01-31T15:06:46.772Z",
"update_at": "2018-01-31T15:06:46.772Z"
}
},
{
"model": "course.lesson",
"pk": 3,
"fields": {
"title": "3 УРОК",
"short_description": "Попробуем оживить персонажа. Выбираем 5 характерных действий для вашего героя и того сюжета, в котором он принимает участие, рисуем эскизы вашего персонажа в разных характерных для него позах и движении.\\r\\n\\r\\nВыделяем 5 характерных эмоций для героя, над которым работаем. По эмоциям подбираем референсы и стилизуем их в своем стиле.",
"course": 11,
"created_at": "2018-01-31T15:07:08.979Z",
"update_at": "2018-01-31T15:07:08.979Z"
}
},
{
"model": "course.material",
"pk": 1,
"fields": {
"title": "Бумага акварельная",
"short_description": "Бумага для работы с акварелью имеет большое значение. Это объясняется тем, что акварель — краска прозрачная, а значит фактура бумаги будет достаточно сильно влиять на внешний вид красочного слоя.",
"created_at": "2018-01-31T14:55:48.394Z",
"update_at": "2018-01-31T14:55:48.394Z"
}
},
{
"model": "course.material",
"pk": 2,
"fields": {
"title": "Кисточки для рисования",
"short_description": "Кисть — инструмент для покраски и живописи. Кисти делаются из щетины и хвостовых волосков различных животных.",
"created_at": "2018-01-31T14:57:37.751Z",
"update_at": "2018-01-31T14:57:37.751Z"
}
},
{
"model": "course.material",
"pk": 3,
"fields": {
"title": "Краски акварельные",
"short_description": "Завод художественных красок «Невская палитра» выпускает акварель 80 лет, сохраняя традиции производства продукции высочайшего качества. Проверенные временем рецептуры, отлаженная технология изготовления сделали акварельные краски визитной карточкой предприятия в России",
"created_at": "2018-01-31T14:58:46.209Z",
"update_at": "2018-01-31T14:58:46.209Z"
}
}
]

@ -25,7 +25,7 @@
</div>
<div class="go__title">Вернуться</div>
</a>
{% if not paid %}
{% if not paid and course.price %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}
@ -106,6 +106,7 @@
</div>
<div class="meta__title">{{ course.created_at | date:"d F Yг." }}</div>
</div>
{% if course.price %}
<div class="meta__item">
<div class="meta__icon">
<svg class="icon icon-money">
@ -114,6 +115,7 @@
</div>
<div class="meta__title">{{ course.price|floatformat:"-2" }}₽</div>
</div>
{% endif %}
</div>
<div class="course__meta meta">
<div class="meta__item">
@ -378,6 +380,7 @@
</div>
<div class="meta__title">{{ course.created_at | date:"d F Yг." }}</div>
</div>
{% if course.price %}
<div class="meta__item">
<div class="meta__icon">
<svg class="icon icon-money">
@ -386,8 +389,9 @@
</div>
<div class="meta__title">{{ course.price|floatformat:"-2" }}₽</div>
</div>
{% endif %}
</div>
{% if not paid %}
{% if not paid and course.price %}
<a
class="go__btn btn{% if pending %} btn_gray{% endif %} btn_md"
{% if user.is_authenticated %}

@ -1,34 +1,44 @@
{% load static %}
{% load rupluralize from plural %}
{% if school_payment %}
<div class="school__page">
<div class="school__block">
<div class="school__month">
<div class="school__month__wrap">
<div class="school__month__title">Январь</div>
<div class="school__month__subtitle">Осталось 14 дней</div>
<div class="school__month__image">
<img class="school__month__image__pic" src="/static/img/order.jpg">
</div>
</div>
<div class="school__block">
<div class="school__month">
<div class="school__month__wrap">
<div class="school__month__title">
Дата окончания:
<p>{{ school_payment.date_end }}</p>
</div>
<div class="school__btn">
<a class="buy__btn btn btn_md" href="#" {% if user.is_authenticated %}
data-popup=".js-popup-buy"
{% else %}
data-popup=".js-popup-auth"
{% endif %}>ПРОДЛИТЬ ШКОЛУ</a>
</div>
<div class="school__btn__prev">
<a class="school__prev__btn btn" href="#">ПОСМОТРЕТЬ ПРЕДЫДУЩИЕ УРОКИ</a>
<div class="school__month__subtitle">Осталось: {{ school_days_left | rupluralize:"день,дня,дней" }}</div>
<div class="school__month__image">
<img class="school__month__image__pic" src="{% static 'img/order.jpg' %}">
</div>
</div>
</div>
<div class="school__block school__list">
{% for course in course_items %}
<div class="school__item">
<div class="school__title">Понедельник</div>
<div class="school__subtitle">Пластилиновая живопись.</div>
<div class="school__content">
<p>Научимся смешивать цвета, получать красивые оттенки и создавать картины из пластилина разными техниками. Узнаем как хранить и ухаживать за такими работами.</p>
</div>
</div>
{% endfor %}
<div class="school__btn">
<a
class="buy__btn btn btn_md"
href="#"
{% if user.is_authenticated %}
data-popup=".js-popup-buy"
{% else %}
data-popup=".js-popup-auth"
{% endif %}>ПРОДЛИТЬ ШКОЛУ</a>
</div>
<div class="school__btn__prev">
<a class="school__prev__btn btn" href="#">ПОСМОТРЕТЬ ПРЕДЫДУЩИЕ УРОКИ</a>
</div>
</div>
<div class="school__block school__list">
{% for school_schedule in school_schedules %}
<div class="school__item">
<div class="school__title">{{ school_schedule }}</div>
<div class="school__subtitle">{{ school_schedule.title }}</div>
<div class="school__content">
<p>{{ school_schedule.description }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}

@ -9,8 +9,9 @@ register = template.Library()
def rupluralize(value, arg):
args = arg.split(',')
try:
value = str(0 if not value or int(value) <= 0 else value)[-1:]
return value + ' ' + args[0 if value == '1' else (1 if value in '234' else 2)]
_value = str(0 if not value or int(value) <= 0 else value)[-1:]
value = value + ' ' + args[0 if _value == '1' else (1 if _value in '234' else 2)]
return value
except:
raise template.TemplateSyntaxError
return ''

@ -1,3 +1,5 @@
from paymentwall import Pingback
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.db.models import Q
@ -188,15 +190,20 @@ class CourseView(DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['next'] = self.request.GET.get('next', None)
context['paid'] = self.object.payments.filter(
user=self.request.user,
authorbalance__status=AuthorBalance.ACCEPTED,
).exists()
context['pending'] = self.object.payments.filter(
user=self.request.user,
authorbalance__status=AuthorBalance.PENDING,
).exists()
if not self.request.user.is_anonymous:
context['next'] = self.request.GET.get('next', None)
context['paid'] = self.object.payments.filter(
user=self.request.user,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).exists()
context['pending'] = self.object.payments.filter(
user=self.request.user,
status=Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW,
).exists()
return context
def get_queryset(self):

@ -0,0 +1,8 @@
{% extends "notification/email/_base.html" %}
{% block content %}
<p style="margin: 0 0 20px">К сожалению вам отказано в выводе средств!</p>
<div style="margin-bottom: 10px;">
<p>{{ author_balance.cause }}</p>
</div>
{% endblock content %}

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-28 11:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0010_auto_20180227_0933'),
]
operations = [
migrations.AddField(
model_name='authorbalance',
name='cause',
field=models.TextField(default='', verbose_name='Причина отказа'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-03-02 07:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0011_authorbalance_cause'),
]
operations = [
migrations.AlterField(
model_name='authorbalance',
name='cause',
field=models.TextField(blank=True, null=True, verbose_name='Причина отказа'),
),
]

@ -0,0 +1,36 @@
# Generated by Django 2.0.2 on 2018-03-04 17:57
import django.core.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('payment', '0012_auto_20180302_0740'),
]
operations = [
migrations.AddField(
model_name='authorbalance',
name='card',
field=models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator(regex='^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\\\d{3})\\d{11})$')]),
),
migrations.AddField(
model_name='authorbalance',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='authorbalance',
name='declined_send_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='authorbalance',
name='update_at',
field=models.DateTimeField(auto_now=True),
),
]

@ -0,0 +1,29 @@
# Generated by Django 2.0.2 on 2018-03-07 06:17
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('payment', '0013_auto_20180304_1757'),
]
operations = [
migrations.AlterModelOptions(
name='payment',
options={'ordering': ('created_at',), 'verbose_name': 'Платеж', 'verbose_name_plural': 'Платежи'},
),
migrations.AddField(
model_name='payment',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='payment',
name='update_at',
field=models.DateTimeField(auto_now=True),
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-03-07 09:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0014_auto_20180307_0617'),
]
operations = [
migrations.AlterField(
model_name='payment',
name='status',
field=models.PositiveSmallIntegerField(choices=[(0, 'regular'), (1, 'goodwill'), (2, 'negative'), (200, 'risk under review'), (201, 'risk reviewed accepted'), (202, 'risk reviewed declined'), (203, 'risk authorization voided'), (12, 'subscription cancelation'), (13, 'subscription expired'), (14, 'subscription payment failed')], null=True, verbose_name='Статус платежа'),
),
]

@ -1,16 +1,21 @@
from django.db import models
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField, JSONField
from constance import config
from paymentwall import Pingback
from polymorphic.models import PolymorphicModel
from django.db import models
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField, JSONField
from django.core.validators import RegexValidator
from django.utils.timezone import now
from apps.course.models import Course
from apps.school.models import SchoolSchedule
from apps.notification.utils import send_email
User = get_user_model()
CREDIT_CARD_RE = r'^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\d{11})$'
class AuthorBalance(models.Model):
IN = 0
@ -34,11 +39,33 @@ class AuthorBalance(models.Model):
commission = models.DecimalField('Комиссия', max_digits=8, decimal_places=2, default=0)
status = models.PositiveSmallIntegerField('Статус', choices=STATUS_CHOICES, default=0)
payment = models.OneToOneField('Payment', on_delete=models.CASCADE, null=True, blank=True, verbose_name='Платёж')
cause = models.TextField('Причина отказа', null=True, blank=True)
card = models.CharField(max_length=20, validators=[RegexValidator(regex=CREDIT_CARD_RE)], null=True, blank=True)
declined_send_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Баланс'
verbose_name_plural = 'Балансы'
def save(self, *args, **kwargs):
self.commission = self.calc_commission()
if self.type == self.OUT:
if self.status == self.DECLINED and not self.declined_send_at:
send_email(
'Отказ вывода средств',
self.author.email,
'notification/email/decline_withdrawal.html',
author_balance=self,
)
self.declined_send_at = now()
super().save(*args, **kwargs)
def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100
class Payment(PolymorphicModel):
PW_STATUS_CHOICES = (
@ -55,16 +82,35 @@ class Payment(PolymorphicModel):
)
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='Пользователь', related_name='payments')
amount = models.DecimalField('Итого', max_digits=8, decimal_places=2, default=0, editable=False)
status = models.PositiveSmallIntegerField('Статус платежа', choices=PW_STATUS_CHOICES, null=True, editable=False)
status = models.PositiveSmallIntegerField('Статус платежа', choices=PW_STATUS_CHOICES, null=True)
data = JSONField('Данные платежа от провайдера', default={}, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Платеж'
verbose_name_plural = 'Платежи'
ordering = ('created_at',)
def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100
def is_deliverable(self):
return self.status in [
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
]
def is_under_review(self):
return self.status == Pingback.PINGBACK_TYPE_RISK_UNDER_REVIEW
def is_cancelable(self):
return self.status in [
Pingback.PINGBACK_TYPE_NEGATIVE,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_DECLINED,
]
class CoursePayment(Payment):
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='Курс', related_name='payments')
@ -82,11 +128,9 @@ class CoursePayment(Payment):
author=self.course.author,
amount=self.amount,
payment=self,
commission=self.calc_commission(),
)
else:
author_balance.amount = self.amount
author_balance.commission = self.calc_commission()
author_balance.save()
@ -99,6 +143,10 @@ class SchoolPayment(Payment):
verbose_name = 'Платеж за школу'
verbose_name_plural = 'Платежи за школу'
def __str__(self):
days = ', '.join([dict(SchoolSchedule.WEEKDAY_CHOICES).get(weekday, '') for weekday in sorted(self.weekdays)])
return days
def save(self, *args, **kwargs):
aggregate = SchoolSchedule.objects.filter(
weekday__in=self.weekdays,

@ -1,3 +1,8 @@
import logging
import json
from datetime import timedelta
from django.contrib import messages
from django.http import HttpResponse
from django.shortcuts import redirect
@ -5,6 +10,7 @@ from django.views.generic import View, TemplateView
from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from paymentwall import Pingback, Product, Widget
@ -13,6 +19,8 @@ from apps.school.models import SchoolSchedule
from .models import AuthorBalance, CoursePayment, SchoolPayment
logger = logging.getLogger('django')
class CourseBuyView(TemplateView):
template_name = 'payment/paymentwall_widget.html'
@ -32,16 +40,17 @@ class CourseBuyView(TemplateView):
)
widget = Widget(
str(request.user.id),
'p1',
'p1_1',
[product],
extra_params={
'lang': 'ru',
'evaluation': 1,
'demo': 1,
'test_mode': 1,
'success_url': host + str(reverse_lazy('payment-success')),
'failure_url': host + str(reverse_lazy('payment-error')),
}
)
return self.render_to_response(context={'widget': widget.get_html_code()})
@ -71,16 +80,17 @@ class SchoolBuyView(TemplateView):
)
widget = Widget(
str(request.user.id),
'p1',
'p1_1',
[product],
extra_params={
'lang': 'ru',
'evaluation': 1,
'demo': 1,
'test_mode': 1,
'success_url': host + str(reverse_lazy('payment-success')),
'failure_url': host + str(reverse_lazy('payment-error')),
}
)
return self.render_to_response(context={'widget': widget.get_html_code()})
@ -113,13 +123,34 @@ class PaymentwallCallbackView(View):
payment = product_payment_class.objects.get(pk=payment_id)
except product_payment_class.DoesNotExist:
return HttpResponse(status=403)
logger.info(
json.dumps(payment_raw_data),
)
payment.status = pingback.get_type()
payment.data = payment_raw_data
if pingback.is_deliverable() and product_type_name == 'school':
school_payment = SchoolPayment.objects.filter(
user=payment.user,
date_start__lte=now(),
date_end__gt=now(),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).last()
if school_payment:
date_start = school_payment.date_end + timedelta(days=1)
date_end = date_start + timedelta(days=30)
else:
date_start = now()
date_end = now() + timedelta(days=30)
payment.date_start = date_start
payment.date_end = date_end
payment.save()
author_balance = getattr(payment, 'author_balance', None)
if author_balance:
if author_balance and author_balance.type == AuthorBalance.IN:
if pingback.is_deliverable():
payment.author_balance.status = AuthorBalance.ACCEPTED
elif pingback.is_under_review():

@ -0,0 +1,93 @@
[
{
"model": "school.schoolschedule",
"pk": 1,
"fields": {
"weekday": 1,
"title": "Акварель",
"description": "Сальвадор Дали родился в Испании 11 мая 1904 года в городе Фигерасе, провинция Жирона, в семье зажиточного нотариуса. По национальности был каталонцем, воспринимал себя в этом качестве и настаивал на этой своей особенности.",
"materials": "У него была сестра, Анна Мария Дали (исп. Anna Maria Dalí, 6 января 1908 — 16 мая 1989), и старший брат (12 октября 1901 — 1 августа 1903), который умер от менингита.",
"age": 0,
"month_price": "500.00",
"day_discount": "0.00"
}
},
{
"model": "school.schoolschedule",
"pk": 2,
"fields": {
"weekday": 2,
"title": "Живопись",
"description": "Позднее в возрасте 5 лет на его могиле родители сказали Сальвадору, что он — реинкарнация своего старшего брата.",
"materials": "В детстве Дали был сообразительным, но заносчивым и неуправляемым ребёнком.",
"age": 0,
"month_price": "500.00",
"day_discount": "0.00"
}
},
{
"model": "school.schoolschedule",
"pk": 3,
"fields": {
"weekday": 3,
"title": "Пластелин",
"description": "Однажды он затеял скандал на торговой площади ради леденца, вокруг собралась толпа, и полицейские попросили хозяина лавки открыть её во время сиесты и подарить мальчику сладость.",
"materials": "Он добивался своего капризами и симуляцией, всегда стремился выделиться и привлечь к себе внимание.",
"age": 0,
"month_price": "500.00",
"day_discount": "0.00"
}
},
{
"model": "school.schoolschedule",
"pk": 4,
"fields": {
"weekday": 4,
"title": "Моторика",
"description": "Многочисленные комплексы[какие?] и фобии, например, страх перед кузнечиками[9], мешали ему включиться в обычную школьную жизнь, завести с детьми обычные связи дружбы и симпатии.",
"materials": "Но, как и любой человек, испытывая сенсорный голод, он искал эмоциональный контакт с детьми любыми способами, стараясь вжиться в их коллектив если не в роли товарища, то в любой другой роли, а точнее той единственной, на которую был способен, — в роли эпатажного и непослушного ребёнка, странного, чудаковатого, всегда поступающего вопреки чужим мнениям.",
"age": 0,
"month_price": "500.00",
"day_discount": "0.00"
}
},
{
"model": "school.schoolschedule",
"pk": 5,
"fields": {
"weekday": 5,
"title": "Акварель",
"description": "Проигрывая в школьных азартных играх, он вёл себя так, будто выиграл, и торжествовал. Иногда без причины затевал драки.",
"materials": "Одноклассники относились к «странному» ребёнку довольно нетерпимо, использовали его страх перед кузнечиками, подсовывали ему за шиворот этих насекомых, чем доводили Сальвадора до истерики, о чём он позднее поведал в своей книге «Тайная жизнь Сальвадора Дали, рассказанная им самим».",
"age": 0,
"month_price": "500.00",
"day_discount": "0.00"
}
},
{
"model": "school.schoolschedule",
"pk": 6,
"fields": {
"weekday": 6,
"title": "Анимация",
"description": "Обучаться изобразительному искусству Дали начал в муниципальной художественной школе. С 1914 по 1918 год воспитывался в Академии братьев ордена маристов в Фигерасе.",
"materials": "Одним из друзей детства был будущий футболист ФК «Барселона» Хосеп Самитьер. В 1916 году, с семьей Рамона Пишó, отправился на каникулы в город Кадакéс, где познакомился с современным искусством.",
"age": 0,
"month_price": "500.00",
"day_discount": "0.00"
}
},
{
"model": "school.schoolschedule",
"pk": 7,
"fields": {
"weekday": 7,
"title": "Персонаж",
"description": "После прихода к власти Каудильо Фрáнко в 1936 году Дали ссорится с сюрреалистами, стоящими на левых позициях, и его исключают из группы.",
"materials": "В ответ Дали: «Сюрреализм — это я».",
"age": 0,
"month_price": "500.00",
"day_discount": "0.00"
}
}
]

@ -0,0 +1,25 @@
import re
from django import forms
from django.utils.translation import ugettext_lazy as _
CREDIT_CARD_RE = r'^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\d{11})$'
class CreditCardField(forms.CharField):
"""
Form field that validates credit card numbers.
"""
default_error_messages = {
'required': _(u'Номер карты обязателен.'),
'invalid': _(u'Неверный номер карты.'),
}
def clean(self, value):
value = value.replace(' ', '').replace('-', '')
if self.required and not value:
raise forms.utils.ValidationError(self.error_messages['required'])
if value and not re.match(CREDIT_CARD_RE, value):
raise forms.utils.ValidationError(self.error_messages['invalid'])
return value

@ -1,35 +1,35 @@
[
{
"model": "user.user",
"pk": 1,
"fields": {
"password": "pbkdf2_sha256$100000$HoGxjmAQy4yo$3u+CrUiFtooddpg16OEUaSSjqUlwgFUBP1gt75hEoJs=",
"last_login": "2018-01-31T14:51:34Z",
"is_superuser": true,
"username": "admin@lil.city",
"first_name": "Lil",
"last_name": "City",
"is_staff": true,
"is_active": true,
"date_joined": "2018-01-28T08:41:19Z",
"email": "admin@lil.city",
"role": 2,
"gender": "n",
"country": "",
"city": "",
"about": "\u0417\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0430 \u041f\u0425\u0423 \u0438\u043c \u041a.\u0410.\u0421\u0430\u0432\u0438\u0446\u043a\u043e\u0433\u043e \u0445\u0443\u0434\u043e\u0436\u043d\u0438\u043a \u0442\u0435\u0430\u0442\u0440\u0430 \u0438 \u043a\u0438\u043d\u043e. \u0420\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u0441 \u043a\u0440\u0443\u043f\u043d\u0435\u0439\u0448\u0438\u043c\u0438 \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0438\u043c\u0438 \u0438 \u0437\u0430\u0440\u0443\u0431\u0435\u0436\u043d\u044b\u043c\u0438 \u0438\u0437\u0434\u0430\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u0430\u043c\u0438. \u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a \u0438 \u043f\u043e\u0431\u0435\u0434\u0438\u0442\u0435\u043b\u044c \u043c\u0435\u0436\u0434\u0443\u043d\u0430\u0440\u043e\u0434\u043d\u044b\u0445 \u0432\u044b\u0441\u0442\u0430\u0432\u043e\u043a. \u041e\u0441\u043d\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \"Lil City\".",
"instagram": null,
"facebook": null,
"twitter": null,
"pinterest": null,
"youtube": null,
"vkontakte": null,
"fb_id": null,
"fb_data": {},
"is_email_proved": true,
"photo": "",
"groups": [],
"user_permissions": []
}
{
"model": "user.user",
"pk": 1,
"fields": {
"password": "pbkdf2_sha256$100000$HoGxjmAQy4yo$3u+CrUiFtooddpg16OEUaSSjqUlwgFUBP1gt75hEoJs=",
"last_login": "2018-01-31T14:51:34Z",
"is_superuser": true,
"username": "admin@lil.city",
"first_name": "Lil",
"last_name": "City",
"is_staff": true,
"is_active": true,
"date_joined": "2018-01-28T08:41:19Z",
"email": "admin@lil.city",
"role": 2,
"gender": "n",
"country": "",
"city": "",
"about": "Закончила ПХУ им К.А.Савицкого художник театра и кино. Работала с крупнейшими российскими и зарубежными издательствами. Участник и победитель международных выставок. Основатель компании \"Lil City\".",
"instagram": null,
"facebook": null,
"twitter": null,
"pinterest": null,
"youtube": null,
"vkontakte": null,
"fb_id": null,
"fb_data": {},
"is_email_proved": true,
"photo": "",
"groups": [],
"user_permissions": []
}
]
}
]

@ -1,6 +1,8 @@
from django import forms
from django.contrib.auth import get_user_model
from .fields import CreditCardField
User = get_user_model()
@ -47,3 +49,8 @@ class UserEditForm(forms.ModelForm):
'vkontakte',
'photo',
)
class WithdrawalForm(forms.Form):
amount = forms.DecimalField(required=True, min_value=2000)
card = CreditCardField(required=True)

@ -22,64 +22,114 @@
</div>
</div>
{% endif %}
<div class="section">
{% if messages %}
<div class="section section_gray section_menu">
<div class="section__center center center_xs">
{% for message in messages %}
<div class="message message_{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if request.user.role == 1 or request.user.role == 2 %}
<div class="section section_gray">
<div class="section__center center center_xs">
<div class="title title_sm">Вывести деньги со счета</div>
<div class="form">
<form class="form" method="POST">{% csrf_token %}
<div class="form__group">
<div class="form__content">На вашем счету {{ request.user.balance }} руб.</div>
<div class="form__field field">
<div class="form__field field{% if form.amount.errors %} error{% endif %}">
<div class="field__label">СУММА</div>
<div class="field__wrap">
<input class="field__input" type="text" placeholder="{{ request.user.balance }}">
<input class="field__input" name="amount" required="" id="id_amount" type="number" pattern="\d+(,\d{2})?" placeholder="{{ request.user.balance }}">
</div>
<div class="field__error">Размер выводимой суммы не должно быть менее 2000 рублей.</div>
{% if form.amount.errors %}
<div class="field__error">{{ form.amount.errors }}</div>
{% endif %}
</div>
<div class="form__field field">
<div class="form__field field{% if form.card.errors %} error{% endif %}">
<div class="field__label">НОМЕР КРЕДИТНОЙ КАРТЫ
<span class="field__required">*</span>
</div>
<div class="field__wrap">
<input class="field__input" type="email" placeholder="0000 0000 0000 0000">
<input class="field__input" name="card" required="" id="id_card" type="text" autocomplete="off" placeholder="0000 0000 0000 0000">
<div class="field__preview">
<img class="field__pic" src="{% static 'img/paysys.png' %}">
</div>
</div>
{% if form.card.errors %}
<div class="field__error">{{ form.card.errors }}</div>
{% endif %}
</div>
</div>
<div class="form__foot">
<button class="form__btn btn btn_md">СОХРАНИТЬ</button>
</div>
</div>
</form>
</div>
</div>
<div class="section">
{% endif %}
<div class="section section_gray">
<div class="section__center center center_xs">
<div class="title title_sm">История платежей</div>
<div class="transactions">
<div class="transactions__wrap">
{% if request.user.role == 1 or request.user.role == 2 %}
{% for balance in request.user.balances.all %}
<div class="transactions__row">
<div class="transactions__cell">{{balance.payment.course.title}}</div>
<div class="transactions__cell">{{balance.amount}}</div>
<div class="transactions__cell">Получено</div>
</div>
{% endfor %}
{% if request.user.payments.all|length %}
{% for payment in request.user.payments.all %}
<div class="transactions__row">
{% if payment.course %}
<div class="transactions__cell">Курс. {{payment.course.title}}</div>
{% else %}
<div class="transactions__cell">
Школа. {% if payment.date_start and payment.date_end %}{{ payment.date_start }} - {{ payment.date_end }}{% endif %}
{{ payment }}
</div>
{% endif %}
{% if payment.balance %}
<div class="transactions__cell">{{payment.balance.amount}}</div>
{% else %}
<div class="transactions__cell">{{payment.amount}}</div>
{% endif %}
{% if payment.balance.type == 1 %}
<div class="transactions__cell">
{% if payment.balance.status == 0 %}
<span class="transactions__cell__pending">Ожидается подтверждение выплаты</span>
{% elif payment.balance.status == 1 %}
<span class="transactions__cell__success">Выплачено</span>
{% else %}
<span class="transactions__cell__error">Выплата отменена</span>
Причина: "{{ payment.balance.cause }}
{% endif %}
</div>
{% else %}
<div class="transactions__cell">
{% if payment.is_deliverable %}
<span class="transactions__cell__success">Получено</span>
{% elif payment.is_under_review %}
<span class="transactions__cell__pending">Ожидается подтверждение оплаты</span>
{% else %}
<span class="transactions__cell__error">Ошибка оплаты</span>
{% endif %}
</div>
{% endif %}
</div>
{% endfor %}
{% else %}
{% for payment in request.user.payments.all %}
<div class="transactions__row">
<div class="transactions__cell">{{payment.course.title}}</div>
<div class="transactions__cell">{{payment.amount}}</div>
<div class="transactions__cell">Получено</div>
</div>
{% endfor %}
Оплат еще не было
{% endif %}
</div>
<div class="transactions__load load">
<button class="load__btn btn">еще</button>
</div>
<!--<div class="transactions__load load">-->
<!--<button class="load__btn btn">еще</button>-->
<!--</div>-->
</div>
</div>
</div>
{% endblock content %}
{% block foot %}
<script>
(new Inputmask('9999 9999 9999 9999', {placeholder: "0"})).mask(document.querySelector('#id_card'));
</script>
{% endblock foot %}

@ -98,7 +98,7 @@
<div class="tabs__item js-tabs-item">
<div class="courses courses_scroll">
<div class="courses__list">
{% include "course/school.html" with course_items=published %}
{% include "course/school.html" %}
</div>
</div>
</div>
@ -120,4 +120,4 @@
</div>
</div>
</div>
{% endblock content %}
{% endblock content %}

@ -1,9 +1,15 @@
import arrow
from io import BytesIO
from PIL import Image
from os.path import splitext
from datetime import timedelta
from paymentwall import Pingback
from django.contrib.auth import login
from django.core.exceptions import ValidationError
from django.shortcuts import render, reverse, redirect
from django.views.generic import DetailView, UpdateView, TemplateView
from django.views.generic import DetailView, UpdateView, TemplateView, FormView
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required, permission_required
@ -11,13 +17,15 @@ from django.contrib.auth.hashers import check_password, make_password
from django.http import Http404
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from apps.auth.tokens import verification_email_token
from apps.course.models import Course
from apps.payment.models import CoursePayment
from apps.notification.utils import send_email
from apps.school.models import SchoolSchedule
from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment
from .forms import UserEditForm
from .forms import UserEditForm, WithdrawalForm
User = get_user_model()
@ -31,6 +39,7 @@ def resend_email_verify(request):
return redirect('user-edit-profile', request.user.id)
@method_decorator(login_required, name='dispatch')
class UserView(DetailView):
model = User
template_name = 'user/profile.html'
@ -47,11 +56,35 @@ class UserView(DetailView):
author=self.object, status=Course.DRAFT
)
context['paid'] = Course.objects.filter(
payments__in=CoursePayment.objects.filter(user=self.object),
payments__in=CoursePayment.objects.filter(
user=self.object,
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
),
).distinct()
school_payment = SchoolPayment.objects.filter(
user=self.object,
date_start__lte=now(),
date_end__gt=now(),
status__in=[
Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL,
Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED,
],
).last()
context['school_payment'] = school_payment
if school_payment and school_payment.date_end:
context['school_days_left'] = (school_payment.date_end - now().date()).days
context['school_schedules'] = SchoolSchedule.objects.filter(
weekday__in=school_payment.weekdays if school_payment else [],
)
return context
@method_decorator(login_required, name='dispatch')
class NotificationEditView(TemplateView):
template_name = 'user/notification-settings.html'
@ -59,13 +92,40 @@ class NotificationEditView(TemplateView):
return super().get(request)
class PaymentHistoryView(TemplateView):
@method_decorator(login_required, name='dispatch')
class PaymentHistoryView(FormView):
template_name = 'user/payment-history.html'
form_class = WithdrawalForm
def get(self, request, pk=None):
return super().get(request)
def post(self, request, pk=None):
form = self.get_form()
if AuthorBalance.objects.filter(created_at__gte=now() - timedelta(days=30)).exists():
messages.error(request, 'Запрос на вывод средств можно сделать только один раз в 30 дней.')
return self.form_invalid(form)
if form.is_valid():
if request.user.balance < form.cleaned_data['amount']:
form.errors['amount'] = 'Сумма для вывода не может быть меньше средств на счету'
return self.form_invalid(form)
AuthorBalance.objects.create(
author=request.user,
type=AuthorBalance.OUT,
amount=form.cleaned_data['amount'],
status=AuthorBalance.PENDING,
card=form.cleaned_data['amount'],
)
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_success_url(self):
success_url = reverse_lazy('user-edit-payments', args=[self.request.user.id])
return success_url
@method_decorator(login_required, name='dispatch')
class UserEditView(UpdateView):
model = User
template_name = 'user/profile-settings.html'

@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
import os
from collections import OrderedDict
from datetime import timedelta
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -215,13 +216,20 @@ CELERY_BROKER_URL = 'redis://redis:6379/0'
CELERY_RESULT_BACKEND = 'redis://redis:6379/1'
CELERY_TASK_SERIALIZER = 'json'
CELERY_BEAT_SCHEDULE = {
'retrieve_photos_from_instagram': {
'task': 'apps.content.tasks.retrieve_photos',
'schedule': timedelta(minutes=2),
'args': (),
},
}
# Dynamic settings
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
CONSTANCE_CONFIG = OrderedDict((
('INSTAGRAM_CLIENT_LOGIN', ('', '')),
('INSTAGRAM_CLIENT_PASSWORD', ('', '')),
('INSTAGRAM_CLIENT_ACCESS_TOKEN', ('7145314808.f6fa114.ce354a5d876041fc9d3db04b0045587d', '')),
('INSTAGRAM_CLIENT_SECRET', ('2334a921425140ccb180d145dcd35b25', '')),
('INSTAGRAM_RESULTS_TAG', ('#lil_акварель', 'Тэг результатов работ.')),
('INSTAGRAM_RESULTS_PATH', ('media/instagram/results/', 'Путь до результатов работ.')),
('SERVICE_COMMISSION', (10, 'Комиссия сервиса в процентах.'))

@ -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">
<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">
<span class="switch__content">
<span class="switch__cell">{{ school_schedule }}</span>
{% comment %} dont delete {% endcomment %}
@ -463,7 +463,7 @@
</div>
<div class="order__foot">
<div class="order__subtitle">Итого, за месяц:</div>
<div class="order__total">1800р.</div>
<div class="order__total order_price_text">1800р.</div>
</div>
</div>
</div>
@ -471,7 +471,7 @@
</div>
<div class="buy__foot">
{% comment %}В ссылке, в параметре запроса weekdays, нужно указать выбранные дни недели{% endcomment %}
<a class="buy__btn btn btn_md" href="{% url 'school-checkout' %}?weekdays=1&weekdays=2">ПЕРЕЙТИ К ОПЛАТЕ</a>
<a class="buy__btn btn btn_md but_btn_popup" data-link="{% url 'school-checkout' %}" href="{% url 'school-checkout' %}?weekdays=1&weekdays=2">ПЕРЕЙТИ К ОПЛАТЕ</a>
</div>
</div>
</div>

@ -6,7 +6,11 @@
<div class="main__center center">
<div class="main__title">Первая онлайн-школа креативного мышления для детей! 5+</div>
<a
{% if not user.is_authenticated %}
data-popup=".js-popup-auth"
{% else %}
data-popup=".js-popup-buy"
{% endif %}
class="main__btn btn"
href="#"
>КУПИТЬ ДОСТУП ОТ 2000р. в мес.</a>
@ -192,42 +196,42 @@
<div class="gallery">
<div class="gallery__grid">
<div class="gallery__item gallery__item_lg">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/0.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/1.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/2.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/3.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/4.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/5.jpg" onerror="this.style.display='none'">
</div>
</div>
<div class="gallery__grid">
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/6.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/7.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/8.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item gallery__item_lg">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/9.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/10.jpg" onerror="this.style.display='none'">
</div>
<div class="gallery__item">
<img class="gallery__pic" src="{% static 'img/box.jpg' %}">
<img class="gallery__pic" src="{% get_media_prefix %}instagram/results/11.jpg" onerror="this.style.display='none'">
</div>
</div>
</div>

@ -24,13 +24,13 @@ from apps.course.views import (
lessoncomment, CourseEditView,
CourseOnModerationView,
)
from apps.course.models import Course
from apps.user.views import (
UserView, UserEditView, NotificationEditView,
PaymentHistoryView, resend_email_verify,
)
from apps.payment.views import CourseBuyView, PaymentwallCallbackView, SchoolBuyView
from apps.school.models import SchoolSchedule
from .views import IndexView
urlpatterns = [
path('admin/', admin.site.urls),
@ -48,6 +48,7 @@ urlpatterns = [
path('lesson/<int:pk>/', LessonView.as_view(), name='lesson'),
path('lesson/<int:lesson_id>/comment', lessoncomment, name='lessoncomment'),
path('payments/ping', PaymentwallCallbackView.as_view(), name='payment-ping'),
path('paymentwall/pingback', PaymentwallCallbackView.as_view(), name='payment-ping-second'),
path('payments/success', TemplateView.as_view(template_name='payment/payment_success.html'), name='payment-success'),
path('payments/error', TemplateView.as_view(template_name='payment/payment_error.html'), name='payment-error'),
path('school/checkout', SchoolBuyView.as_view(), name='school-checkout'),
@ -60,15 +61,7 @@ urlpatterns = [
path('privacy', TemplateView.as_view(template_name="templates/lilcity/privacy_policy.html"), name='privacy'),
path('terms', TemplateView.as_view(template_name="templates/lilcity/terms.html"), name='terms'),
path('refund-policy', TemplateView.as_view(template_name="templates/lilcity/refund_policy.html"), name='refund_policy'),
path('',
TemplateView.as_view(
template_name="templates/lilcity/main.html",
extra_context={
'course_items': Course.objects.filter(status=Course.PUBLISHED)[:3],
'school_schedules': SchoolSchedule.objects.all(),
}),
name='index'
),
path('', IndexView.as_view(), name='index'),
path('api/v1/', include(('api.v1.urls', 'api_v1'))),
path('test', TemplateView.as_view(template_name="templates/lilcity/test.html"), name='test'),
]

@ -0,0 +1,16 @@
from django.views.generic import TemplateView
from apps.course.models import Course
from apps.school.models import SchoolSchedule
class IndexView(TemplateView):
template_name = 'templates/lilcity/main.html'
def get_context_data(self):
context = super().get_context_data()
context.update({
'course_items': Course.objects.filter(status=Course.PUBLISHED)[:3],
'school_schedules': SchoolSchedule.objects.all(),
})
return context

@ -1,4 +1,5 @@
# Python-3.6
requests==2.18.4
Django==2.0.2
django-anymail[mailgun]==1.2
# paymentwall-python==1.0.7
@ -18,4 +19,5 @@ drf-yasg[validation]==1.4.0
django-silk==2.0.0
django-cors-headers==2.1.0
django-constance[database]==2.1.0
InstagramAPI==1.0.2
# python-instagram==1.3.2
git+https://github.com/ivlevdenis/python-instagram.git

@ -6161,6 +6161,11 @@
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"inputmask": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/inputmask/-/inputmask-3.3.11.tgz",
"integrity": "sha1-FCHJSuKMPc0bTSYze1CLs0mY4tg="
},
"interpret": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",

@ -57,6 +57,7 @@
"babel-polyfill": "^6.26.0",
"history": "^4.7.2",
"ilyabirman-likely": "^2.3.0",
"inputmask": "^3.3.11",
"jquery": "^3.3.1",
"lodash.debounce": "^4.0.8",
"moment": "^2.20.1",

@ -1,6 +1,7 @@
import $ from 'jquery';
import Inputmask from "inputmask";
import SmoothScroll from 'smooth-scroll/dist/js/smooth-scroll';
window.Inputmask = Inputmask;
$(document).ready(function () {
// Добавляем заголовок X-CSRFToken для всех AJAX запросов JQuery.
$.ajaxSetup({

@ -47,4 +47,47 @@ $(document).ready(function () {
popup.removeClass('open');
}, 300);
}
var selectedWeekdays = {};
$(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};
} else {
console.log('not checked');
delete selectedWeekdays[weekday];
}
updateCart();
});
function updateCart(){
var days = ['', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'];
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)+'р.';
} else {
text = price+'p.';
}
$('.order_price_text').html(text);
$('.order__days').html((daysText.length) ? daysText.join(', '):'Ничего не выбрано');
var link = $('.but_btn_popup').data('link');
link = link+'?'+decodeURIComponent($.param({weekdays: weekdays}, true));
$('.but_btn_popup').attr('href', link);
}
updateCart();
});

@ -2648,6 +2648,8 @@ a.grey-link
padding: 2px
background-image: linear-gradient(-225deg, #FFE2EB 0%, #D8F5F5 100%)
border-radius: 8px
&__days
min-height: 58px
&__wrap
padding: 20px 30px 30px
background: white
@ -2681,6 +2683,8 @@ a.grey-link
padding: 20px 20px 0
&__total
margin-left: auto
del
color: #a0a0a0
.lock
padding: 50px 60px 40px
@ -2871,6 +2875,12 @@ a.grey-link
&__cell
padding: 0 10px
font-size: 13px
&__success
color: $green
&__pending
color: $gray
&__error
color: $pink
&:first-child
+fb
font-size: 12px
@ -3339,10 +3349,14 @@ a
flex-wrap: wrap
&__block
flex: 0 0 calc(50% - 20px)
+m
flex: 0 0 calc(100% - 20px)
&__list
display: flex
margin: 0 -10px
flex-wrap: wrap
+m
margin-top: 20px
&__item
display: block
margin: 0 10px 40px 10px
@ -3366,10 +3380,14 @@ a
margin-top: 20px
a
width: 70%
+m
width: 100%;
&__btn__prev
margin-top: 12px
a
width: 70%
+m
width: 100%;
&__month
margin-bottom: 20px
flex: 0 0 320px
@ -3377,6 +3395,8 @@ a
background-image: linear-gradient(-225deg, #FFE2EB 0%, #D8F5F5 100%)
width: 70%
padding: 2px
+m
width: 100%;
&__wrap
padding: 5px 0 10px
background: white

@ -347,6 +347,14 @@ autoprefixer@^6.3.1, autoprefixer@^6.3.3:
postcss "^5.2.16"
postcss-value-parser "^3.2.3"
autosize-input@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/autosize-input/-/autosize-input-0.4.0.tgz#a5cdb43110e31f6a1d9fb695ccb747495af0bb5e"
autosize@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.0.tgz#7a0599b1ba84d73bd7589b0d9da3870152c69237"
aws-sign2@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@ -3282,6 +3290,10 @@ ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
ilyabirman-likely@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/ilyabirman-likely/-/ilyabirman-likely-2.3.0.tgz#4462becc5dedeb36b74bf4ba339a0ceab820785f"
immutable@3.8.2, immutable@^3.7.6:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
@ -3979,6 +3991,10 @@ lodash.clonedeep@^4.3.2:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
lodash.defaults@^4.0.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
@ -6768,6 +6784,13 @@ void-elements@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
vue-autosize@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/vue-autosize/-/vue-autosize-1.0.2.tgz#d4159848a7c0f839d49dea45412ce472cdd3a48a"
dependencies:
autosize "^4.0.0"
autosize-input "^0.4.0"
vue-hot-reload-api@^2.2.0:
version "2.2.4"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.2.4.tgz#683bd1d026c0d3b3c937d5875679e9a87ec6cd8f"
@ -6823,6 +6846,10 @@ vuejs-datepicker@^0.9.25:
version "0.9.26"
resolved "https://registry.yarnpkg.com/vuejs-datepicker/-/vuejs-datepicker-0.9.26.tgz#63e8ab8f3725663f3f90543545d3c097b8ff8f43"
vuelidate@^0.6.1:
version "0.6.2"
resolved "https://registry.yarnpkg.com/vuelidate/-/vuelidate-0.6.2.tgz#b417f5367407b047a2548119b25db56fe2bd5753"
warning@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"

Loading…
Cancel
Save