Merge branch 'master' of https://gitlab.com/lilcity/backend into feature/LIL-559
commit
4bfa505a56
121 changed files with 3104 additions and 1030 deletions
@ -0,0 +1,75 @@ |
|||||||
|
from django.contrib.auth import get_user_model |
||||||
|
from rest_framework import serializers |
||||||
|
|
||||||
|
from api.v1.serializers.content import ContentSerializer, ContentCreateSerializer, ImageObjectSerializer |
||||||
|
from api.v1.serializers.mixins import DispatchContentMixin |
||||||
|
|
||||||
|
from apps.content.models import (Contest, ContestWork) |
||||||
|
|
||||||
|
User = get_user_model() |
||||||
|
|
||||||
|
|
||||||
|
class ContestSerializer(serializers.ModelSerializer): |
||||||
|
cover = ImageObjectSerializer() |
||||||
|
content = ContentSerializer(many=True) |
||||||
|
|
||||||
|
class Meta: |
||||||
|
model = Contest |
||||||
|
fields = ['title', 'description', 'slug', 'cover', |
||||||
|
'date_start', 'date_end', 'active', 'content', 'finished'] |
||||||
|
|
||||||
|
|
||||||
|
class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer): |
||||||
|
content = serializers.ListSerializer( |
||||||
|
child=ContentCreateSerializer(), |
||||||
|
required=False, |
||||||
|
) |
||||||
|
|
||||||
|
class Meta: |
||||||
|
model = Contest |
||||||
|
fields = '__all__' |
||||||
|
|
||||||
|
def create(self, validated_data): |
||||||
|
content = validated_data.pop('content', []) |
||||||
|
contest = super().create(validated_data) |
||||||
|
self.dispatch_content(contest, content) |
||||||
|
return contest |
||||||
|
|
||||||
|
def update(self, instance, validated_data): |
||||||
|
content = validated_data.pop('content', []) |
||||||
|
contest = super().update(instance, validated_data) |
||||||
|
self.dispatch_content(contest, content) |
||||||
|
return contest |
||||||
|
|
||||||
|
def to_representation(self, instance): |
||||||
|
return ContestSerializer(instance=instance, context=self.context).to_representation(instance) |
||||||
|
|
||||||
|
|
||||||
|
class ContestWorkSerializer(serializers.ModelSerializer): |
||||||
|
image = ImageObjectSerializer() |
||||||
|
likes = serializers.SerializerMethodField() |
||||||
|
user_liked = serializers.SerializerMethodField() |
||||||
|
|
||||||
|
class Meta: |
||||||
|
model = ContestWork |
||||||
|
fields = ['id', 'user', 'contest', 'image', 'child_full_name', 'age', |
||||||
|
'created_at', 'likes', 'user_liked', 'img_width', 'img_height'] |
||||||
|
|
||||||
|
def get_likes(self, instance): |
||||||
|
return instance.likes.filter(user__is_active=True).count() |
||||||
|
|
||||||
|
def get_user_liked(self, instance): |
||||||
|
# FIXME |
||||||
|
user = self.context['request'].query_params.get('current_user') |
||||||
|
if user: |
||||||
|
user = User.objects.get(pk=user) |
||||||
|
return instance.likes.filter(user=user).exists() if user else False |
||||||
|
|
||||||
|
|
||||||
|
class ContestWorkCreateSerializer(serializers.ModelSerializer): |
||||||
|
class Meta: |
||||||
|
model = ContestWork |
||||||
|
fields = '__all__' |
||||||
|
|
||||||
|
def to_representation(self, instance): |
||||||
|
return ContestWorkSerializer(instance=instance, context=self.context).to_representation(instance) |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
from django.contrib.auth.backends import ModelBackend |
||||||
|
from django.contrib.auth import get_user_model |
||||||
|
|
||||||
|
User = get_user_model() |
||||||
|
|
||||||
|
|
||||||
|
class CaseInsensitiveModelBackend(ModelBackend): |
||||||
|
|
||||||
|
def authenticate(self, request, username=None, password=None, **kwargs): |
||||||
|
if username is None: |
||||||
|
username = kwargs.get(User.USERNAME_FIELD) |
||||||
|
try: |
||||||
|
user = User.objects.get(**{f'{User.USERNAME_FIELD}__iexact': username}) |
||||||
|
if user.check_password(password) and self.user_can_authenticate(user): |
||||||
|
return user |
||||||
|
except User.DoesNotExist: |
||||||
|
return None |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-13 13:06 |
||||||
|
|
||||||
|
from django.conf import settings |
||||||
|
from django.db import migrations, models |
||||||
|
import django.db.models.deletion |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('course', '0040_course_age'), |
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||||
|
('content', '0020_auto_20180424_1607'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.CreateModel( |
||||||
|
name='Contest', |
||||||
|
fields=[ |
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||||
|
('title', models.CharField(max_length=255)), |
||||||
|
('description', models.TextField(blank=True, default='', max_length=1000)), |
||||||
|
('slug', models.SlugField(allow_unicode=True, blank=True, max_length=100, null=True, unique=True)), |
||||||
|
('date_start', models.DateField(blank=True, null=True, verbose_name='Дата начала')), |
||||||
|
('date_end', models.DateField(blank=True, null=True, verbose_name='Дата окончания')), |
||||||
|
('active', models.BooleanField(default=True)), |
||||||
|
('cover', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contest_covers', to='content.ImageObject', verbose_name='Фоновая картинка')), |
||||||
|
], |
||||||
|
), |
||||||
|
migrations.CreateModel( |
||||||
|
name='ContestWork', |
||||||
|
fields=[ |
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||||
|
('child_full_name', models.CharField(max_length=255)), |
||||||
|
('age', models.SmallIntegerField()), |
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)), |
||||||
|
('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.Contest')), |
||||||
|
('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contest_work_images', to='content.ImageObject', verbose_name='Работа участника')), |
||||||
|
('likes', models.ManyToManyField(blank=True, to='course.Like')), |
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
||||||
|
], |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='content', |
||||||
|
name='contest', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content', to='content.Contest', verbose_name='Конкурс'), |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-15 21:29 |
||||||
|
|
||||||
|
from django.db import migrations |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('content', '0021_auto_20180813_1306'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AlterModelOptions( |
||||||
|
name='contestwork', |
||||||
|
options={'ordering': ('-created_at',), 'verbose_name': 'Конкурсная работа', 'verbose_name_plural': 'Конкурсные работы'}, |
||||||
|
), |
||||||
|
] |
||||||
@ -1,32 +1,32 @@ |
|||||||
{% load thumbnail %} |
{% load thumbnail %} |
||||||
{% if results %} |
{% if results %} |
||||||
<div class="title">Галерея итогов обучения</div> |
<div class="title">Галерея итогов обучения</div> |
||||||
<div class="examples gallery"> |
<div class="examples gallery"> |
||||||
{% for image in course.gallery.gallery_images.all %} |
{% for image in course.gallery.gallery_images.all %} |
||||||
<div class="examples__item"> |
<div class="examples__item"> |
||||||
<a href="{{ image.img.image.url }}"> |
<a href="{{ image.img.image.url }}"> |
||||||
{% thumbnail image.img.image "165x165" crop="center" as im %} |
{% thumbnail image.img.image "165x165" crop="center" as im %} |
||||||
<img class="examples__pic" src="{{ im.url }}"> |
<img class="examples__pic" src="{{ im.url }}"> |
||||||
{% endthumbnail %} |
{% endthumbnail %} |
||||||
</a> |
</a> |
||||||
</div> |
</div> |
||||||
{% endfor %} |
{% endfor %} |
||||||
</div> |
</div> |
||||||
{% else %} |
{% else %} |
||||||
<div class="section section_gradient"> |
<div class="section section_gradient"> |
||||||
<div class="section__center center center_sm"> |
<div class="section__center center center_sm"> |
||||||
<div class="title">{{ content.title }}</div> |
<div class="title">{{ content.title }}</div> |
||||||
<div class="examples gallery"> |
<div class="examples gallery"> |
||||||
{% for image in content.gallery_images.all %} |
{% for image in content.gallery_images.all %} |
||||||
<div class="examples__item"> |
<div class="examples__item"> |
||||||
<a href="{{ image.img.image.url }}"> |
<a href="{{ image.img.image.url }}"> |
||||||
{% thumbnail image.img.image "165x165" crop="center" as im %} |
{% thumbnail image.img.image "165x165" crop="center" as im %} |
||||||
<img class="examples__pic" src="{{ im.url }}"> |
<img class="examples__pic" src="{{ im.url }}"> |
||||||
{% endthumbnail %} |
{% endthumbnail %} |
||||||
</a> |
</a> |
||||||
</div> |
</div> |
||||||
{% endfor %} |
{% endfor %} |
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
{% endif %} |
{% endif %} |
||||||
@ -1,10 +1,10 @@ |
|||||||
<div class="section section_border"> |
<div class="section section_border"> |
||||||
<div class="section__center center center_sm"> |
<div class="section__center center center_sm"> |
||||||
<div class="content-block title"> |
<div class="content-block title"> |
||||||
{{ content.title }} |
{{ content.title }} |
||||||
</div> |
</div> |
||||||
<div> |
<div> |
||||||
<img class="content-block pic" src="{{ content.img.image.url }}" alt=""> |
<img class="content-block pic" src="{{ content.img.image.url }}" alt=""> |
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
@ -1,10 +1,10 @@ |
|||||||
<div class="section section_border"> |
<div class="section section_border"> |
||||||
<div class="section__center center center_sm"> |
<div class="section__center center center_sm"> |
||||||
<div class="content-block title"> |
<div class="content-block title"> |
||||||
{{ content.title }} |
{{ content.title }} |
||||||
</div> |
</div> |
||||||
<div class="content-block text" style="margin-bottom:0"> |
<div class="content-block text" style="margin-bottom:0"> |
||||||
{{ content.txt | safe }} |
{{ content.txt | safe }} |
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
{% extends "templates/lilcity/index.html" %} |
||||||
|
|
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<upload-contest-work contest-id="{{ contest.id }}"></upload-contest-work> |
||||||
|
<div class="main main_default" {% if contest.cover %}style="background-image: url({{ contest.cover.image.url }});"{% endif %}> |
||||||
|
<div class="main__center center"> |
||||||
|
<div class="main__title"> |
||||||
|
{{ contest.title }} |
||||||
|
</div> |
||||||
|
<div class="main__subtitle"> |
||||||
|
{{ contest.description }} |
||||||
|
</div> |
||||||
|
<div class="main__actions"> |
||||||
|
{% if not contest_work_uploaded and not contest.finished %} |
||||||
|
<a class="main__btn btn" href="" |
||||||
|
{% if request.user.is_authenticated %}data-show-upload-contest-work |
||||||
|
{% else %}data-popup=".js-popup-auth"{% endif %}>Загрузить свою работу</a> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div style="text-align: center;"> |
||||||
|
{% for content in contest.content.all %} |
||||||
|
|
||||||
|
{% with template="content/blocks/"|add:content.ctype|add:".html" %} |
||||||
|
{% include template %} |
||||||
|
{% endwith %} |
||||||
|
|
||||||
|
{% endfor %} |
||||||
|
<div class="section"> |
||||||
|
<div class="section__center center"> |
||||||
|
<a id="gallery" name="gallery"> |
||||||
|
<div class="title title_center">Галерея</div> |
||||||
|
</a> |
||||||
|
<div class="text"> |
||||||
|
{% if not contest_work_uploaded and not contest.finished %} |
||||||
|
<a class="btn" href="" |
||||||
|
{% if request.user.is_authenticated %}data-show-upload-contest-work |
||||||
|
{% else %}data-popup=".js-popup-auth"{% endif %}>Загрузить свою работу</a> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
<contest-works contest-id="{{ contest.id }}" autoload="true"></contest-works> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% endblock content %} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
{% extends "templates/lilcity/edit_index.html" %} |
||||||
|
{% load static %} |
||||||
|
|
||||||
|
{% block title %} |
||||||
|
{% if object %} |
||||||
|
Редактирование конкурса {{ object.title }} |
||||||
|
{% else %} |
||||||
|
Создание конкурса |
||||||
|
{% endif %} |
||||||
|
{% endblock title %} |
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<contest-redactor {% if object and object.id %}:contest-id="{{ object.id }}"{% endif %}></contest-redactor> |
||||||
|
{% endblock content %} |
||||||
|
{% block foot %} |
||||||
|
<script type="text/javascript" src="{% static 'contestRedactor.js' %}"></script> |
||||||
|
<link rel="stylesheet" href="{% static 'contestRedactor.css' %}" /> |
||||||
|
{% endblock foot %} |
||||||
@ -0,0 +1,118 @@ |
|||||||
|
{% extends "templates/lilcity/index.html" %} |
||||||
|
{% load rupluralize from plural %} |
||||||
|
{% load static %} |
||||||
|
{% block title %}Конкурс Lil.School{% endblock title %} |
||||||
|
{% block description %}Работа {{ contest_work.child_full_name }}, {{ contest_work.age | rupluralize:"год,года,лет" }}{% endblock description%} |
||||||
|
{% block ogdescription %}Работа {{ contest_work.child_full_name }}, {{ contest_work.age | rupluralize:"год,года,лет" }}{% endblock ogdescription %} |
||||||
|
{% block ogimage %}http://{{request.META.HTTP_HOST}}{{ contest_work.image.image.url }}{% endblock ogimage %} |
||||||
|
{% block head %} |
||||||
|
<meta property="og:image:height" content="{{ contest_work.image.image.height }}"> |
||||||
|
<meta property="og:image:width" content="{{ contest_work.image.image.width }}"> |
||||||
|
{% endblock head %} |
||||||
|
|
||||||
|
{% block content %} |
||||||
|
<div class="section" style="padding-bottom: 25px;"> |
||||||
|
<div class="section__center center center_sm"> |
||||||
|
<div class="go"> |
||||||
|
<a class="go__item" href="{% url 'contest' contest_work.contest.slug %}"> |
||||||
|
<div class="go__arrow"> |
||||||
|
<svg class="icon icon-arrow-left"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-left"></use> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
<div class="go__title">Вернуться к галерее</div> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="contest-work section__center center center_sm"> |
||||||
|
<div class="contest-work__img-wrap"> |
||||||
|
<img class="contest-work__img" src="{{ contest_work.image.image.url }}"> |
||||||
|
</div> |
||||||
|
<div class="contest-work__info"> |
||||||
|
<div class="contest-work__bio"> |
||||||
|
<div>{{ contest_work.child_full_name }}</div> |
||||||
|
<div class="contest-work__age">{{ contest_work.age | rupluralize:"год,года,лет" }}</div> |
||||||
|
</div> |
||||||
|
<div class="contest-work__likes"> |
||||||
|
<likes obj-type="contest_work" obj-id="{{ contest_work.id }}" |
||||||
|
{% if user_liked %}:user-liked="true"{% endif %} likes="{{ likes_count }}"></likes> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="section" style="padding: 0;"> |
||||||
|
<div class="section__center center center_sm"> |
||||||
|
<div class="go"> |
||||||
|
{% if prev_contest_work %} |
||||||
|
<a class="go__item" href="{% url 'contest_work' prev_contest_work.id %}"> |
||||||
|
<div class="go__arrow"> |
||||||
|
<svg class="icon icon-arrow-left"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-left"></use> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
<div class="go__title">Предыдущая работа</div> |
||||||
|
</a> |
||||||
|
{% else %} |
||||||
|
<div class="go__item"></div> |
||||||
|
{% endif %} |
||||||
|
{% if next_contest_work %} |
||||||
|
<a class="go__item" href="{% url 'contest_work' next_contest_work.id %}"> |
||||||
|
<div class="go__title">Следующая работа</div> |
||||||
|
<div class="go__arrow"> |
||||||
|
<svg class="icon icon-arrow-right"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-right"></use> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
</a> |
||||||
|
{% else %} |
||||||
|
<div class="go__item"></div> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="section"> |
||||||
|
<div class="section__center center center_sm"> |
||||||
|
{% include 'templates/blocks/share.html' with share_object_name='работой' %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="section section_gray"> |
||||||
|
<div class="section__center center center_sm"> |
||||||
|
<div class="title">Оставьте комментарий:</div> |
||||||
|
<div class="questions"> |
||||||
|
{% if request.user.is_authenticated %} |
||||||
|
<form class="questions__form" method="post" action="{% url 'contest_work_comment' contest_work_id=contest_work.id %}"> |
||||||
|
<input type="hidden" name="reply_id"> |
||||||
|
<div class="questions__ava ava"> |
||||||
|
<img |
||||||
|
class="ava__pic" |
||||||
|
{% if request.user.photo %} |
||||||
|
src="{{ request.user.photo.url }}" |
||||||
|
{% else %} |
||||||
|
src="{% static 'img/user_default.jpg' %}" |
||||||
|
{% endif %} |
||||||
|
> |
||||||
|
</div> |
||||||
|
<div class="questions__wrap"> |
||||||
|
<div class="questions__reply-info">В ответ на |
||||||
|
<a href="" class="questions__reply-anchor">этот комментарий</a>. |
||||||
|
<a href="#" class="questions__reply-cancel grey-link">Отменить</a> |
||||||
|
</div> |
||||||
|
<div class="questions__field"> |
||||||
|
<textarea class="questions__textarea"></textarea> |
||||||
|
</div> |
||||||
|
<button class="questions__btn btn btn_light">ОТПРАВИТЬ</button> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
{% else %} |
||||||
|
<div>Только зарегистрированные пользователи могут оставлять комментарии.</div> |
||||||
|
{% endif %} |
||||||
|
<div class="questions__list"> |
||||||
|
{% include "templates/blocks/comments.html" with object=contest_work %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% endblock content %} |
||||||
@ -1,2 +1,112 @@ |
|||||||
from django.shortcuts import render |
from django.contrib.auth.decorators import login_required |
||||||
|
from django.http import JsonResponse |
||||||
|
from django.shortcuts import get_object_or_404 |
||||||
|
from django.template import loader |
||||||
|
from django.utils.decorators import method_decorator |
||||||
|
from django.views.decorators.csrf import csrf_exempt |
||||||
|
from django.views.decorators.http import require_http_methods |
||||||
|
from django.views.generic import TemplateView, DetailView |
||||||
|
|
||||||
|
from apps.content.models import Contest, ContestWork |
||||||
|
from apps.course.models import ContestWorkComment |
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name='dispatch') |
||||||
|
class ContestEditView(TemplateView): |
||||||
|
template_name = 'content/contest_edit.html' |
||||||
|
query_pk_and_slug = True |
||||||
|
|
||||||
|
def get(self, request, slug=None, lesson=None): |
||||||
|
if slug: |
||||||
|
self.object = get_object_or_404(Contest, slug=slug) |
||||||
|
else: |
||||||
|
self.object = Contest() |
||||||
|
|
||||||
|
return super().get(request) |
||||||
|
|
||||||
|
def get_context_data(self): |
||||||
|
context = super().get_context_data() |
||||||
|
context['object'] = self.object |
||||||
|
return context |
||||||
|
|
||||||
|
|
||||||
|
class ContestView(DetailView): |
||||||
|
model = Contest |
||||||
|
context_object_name = 'contest' |
||||||
|
template_name = 'content/contest.html' |
||||||
|
query_pk_and_slug = True |
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs): |
||||||
|
context = super().get_context_data() |
||||||
|
if self.request.user.is_authenticated: |
||||||
|
context['contest_work_uploaded'] = ContestWork.objects.filter(user=self.request.user).exists() |
||||||
|
return context |
||||||
|
|
||||||
|
|
||||||
|
class ContestWorkView(DetailView): |
||||||
|
model = ContestWork |
||||||
|
context_object_name = 'contest_work' |
||||||
|
template_name = 'content/contest_work.html' |
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs): |
||||||
|
context = super().get_context_data() |
||||||
|
prev_contest_work = ContestWork.objects.filter(created_at__gt=self.object.created_at)[:1] |
||||||
|
if prev_contest_work: |
||||||
|
context['prev_contest_work'] = prev_contest_work[0] |
||||||
|
next_contest_work = ContestWork.objects.filter(created_at__lt=self.object.created_at)[:1] |
||||||
|
if next_contest_work: |
||||||
|
context['next_contest_work'] = next_contest_work[0] |
||||||
|
|
||||||
|
context['user_liked'] = self.object.likes.filter(user=self.request.user).exists() \ |
||||||
|
if self.request.user.is_authenticated else False |
||||||
|
context['likes_count'] = self.object.likes.filter(user__is_active=True).count() |
||||||
|
return context |
||||||
|
|
||||||
|
|
||||||
|
@login_required |
||||||
|
@csrf_exempt |
||||||
|
@require_http_methods(['POST']) |
||||||
|
def contest_work_comment(request, contest_work_id): |
||||||
|
try: |
||||||
|
contest_work = ContestWork.objects.get(id=contest_work_id) |
||||||
|
except ContestWork.DoesNotExist: |
||||||
|
return JsonResponse({ |
||||||
|
'success': False, |
||||||
|
'errors': ['Contest_work with id f{contest_work_id} not found'] |
||||||
|
}, status=400) |
||||||
|
else: |
||||||
|
reply_to = request.POST.get('reply_id', 0) |
||||||
|
comment = request.POST.get('comment', '') |
||||||
|
if not comment: |
||||||
|
return JsonResponse({ |
||||||
|
'success': False, |
||||||
|
'errors': ['Comment can not be empty'] |
||||||
|
}, status=400) |
||||||
|
|
||||||
|
if not int(reply_to): |
||||||
|
contest_work_comment = ContestWorkComment.objects.create( |
||||||
|
author=request.user, |
||||||
|
content=comment, |
||||||
|
contest_work=contest_work, |
||||||
|
) |
||||||
|
else: |
||||||
|
try: |
||||||
|
_contest_work_comment = ContestWorkComment.objects.get(id=reply_to) |
||||||
|
except ContestWorkComment.DoesNotExist: |
||||||
|
return JsonResponse({ |
||||||
|
'success': False, |
||||||
|
'errors': ['LessonComment with id f{reply_to} not found'] |
||||||
|
}, status=400) |
||||||
|
else: |
||||||
|
contest_work_comment = ContestWorkComment.objects.create( |
||||||
|
author=request.user, |
||||||
|
content=comment, |
||||||
|
contest_work=contest_work, |
||||||
|
parent=_contest_work_comment, |
||||||
|
) |
||||||
|
ctx = {'node': contest_work_comment, 'user': request.user} |
||||||
|
html = loader.render_to_string('templates/blocks/comment.html', ctx) |
||||||
|
return JsonResponse({ |
||||||
|
'success': True, |
||||||
|
'comment': html, |
||||||
|
}) |
||||||
|
|||||||
@ -0,0 +1,18 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-08 01:54 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('course', '0039_lesson_position'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AddField( |
||||||
|
model_name='course', |
||||||
|
name='age', |
||||||
|
field=models.SmallIntegerField(choices=[(0, ''), (1, 'до 5'), (2, '5-7'), (3, '7-9'), (4, '9-12'), (5, '12-15'), (6, '15-18'), (7, 'от 18')], default=0), |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-13 13:06 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
import django.db.models.deletion |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('content', '0021_auto_20180813_1306'), |
||||||
|
('course', '0040_course_age'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.CreateModel( |
||||||
|
name='ContestWorkComment', |
||||||
|
fields=[ |
||||||
|
('comment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='course.Comment')), |
||||||
|
('contest_work', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='content.ContestWork')), |
||||||
|
], |
||||||
|
options={ |
||||||
|
'ordering': ('tree_id', 'lft'), |
||||||
|
'abstract': False, |
||||||
|
'base_manager_name': 'objects', |
||||||
|
}, |
||||||
|
bases=('course.comment',), |
||||||
|
), |
||||||
|
migrations.AlterModelOptions( |
||||||
|
name='course', |
||||||
|
options={'ordering': ['-is_featured'], 'verbose_name': 'Курс', 'verbose_name_plural': 'Курсы'}, |
||||||
|
), |
||||||
|
migrations.RemoveField( |
||||||
|
model_name='course', |
||||||
|
name='age', |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-16 16:45 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('course', '0041_auto_20180813_1306'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AddField( |
||||||
|
model_name='like', |
||||||
|
name='ip', |
||||||
|
field=models.GenericIPAddressField(blank=True, null=True), |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-24 21:32 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
import django.db.models.deletion |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('course', '0042_like_ip'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AlterField( |
||||||
|
model_name='course', |
||||||
|
name='cover', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='course_covers', to='content.ImageObject', verbose_name='Обложка курса'), |
||||||
|
), |
||||||
|
migrations.AlterField( |
||||||
|
model_name='lesson', |
||||||
|
name='cover', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lesson_covers', to='content.ImageObject', verbose_name='Обложка урока'), |
||||||
|
), |
||||||
|
migrations.AlterField( |
||||||
|
model_name='material', |
||||||
|
name='cover', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='material_covers', to='content.ImageObject', verbose_name='Обложка материала'), |
||||||
|
), |
||||||
|
] |
||||||
@ -1,5 +1,6 @@ |
|||||||
{% for cat in category_items %} |
{% for cat in category_items %} |
||||||
<div class="select__option js-select-option{% if category and category.0 == cat.title %} active{% endif %}" data-category-option data-category-name="{{ cat.title }}" data-category-url="{% url 'courses' %}?category={{ cat.title }}"> |
<div class="select__option js-select-option{% if category == cat.id %} active{% endif %}" |
||||||
|
data-category-option data-category-name="{{ cat.title }}" data-category-url="{% url 'courses' %}?category={{ cat.id }}"> |
||||||
<div class="select__title">{{ cat.title }}</div> |
<div class="select__title">{{ cat.title }}</div> |
||||||
</div> |
</div> |
||||||
{% endfor %} |
{% endfor %} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
from decimal import Decimal |
||||||
|
from django.core.management.base import BaseCommand |
||||||
|
from django.db.models import F |
||||||
|
|
||||||
|
from apps.payment.models import Payment, AuthorBalance |
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand): |
||||||
|
help = 'Fix payment and author balance amount based on payment.data.effective_price_amount' |
||||||
|
|
||||||
|
def handle(self, *args, **options): |
||||||
|
|
||||||
|
for payment in Payment.objects.exclude(data__effective_price_amount=''): |
||||||
|
if payment.data.get('effective_price_amount'): |
||||||
|
payment.amount = Decimal(payment.data.get('effective_price_amount')) |
||||||
|
payment.save() |
||||||
|
|
||||||
|
for ab in AuthorBalance.objects.exclude(payment__amount=F('amount')).select_related('payment'): |
||||||
|
ab.amount = ab.payment.amount |
||||||
|
ab.save() |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,20 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-24 19:56 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
import django.db.models.deletion |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('content', '0022_auto_20180815_2129'), |
||||||
|
('school', '0018_auto_20180629_1501'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AddField( |
||||||
|
model_name='schoolschedule', |
||||||
|
name='cover', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schoolschedule_cover', to='content.ImageObject', verbose_name='Обложка дня'), |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-24 21:32 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
import django.db.models.deletion |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('school', '0019_schoolschedule_cover'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AlterField( |
||||||
|
model_name='livelesson', |
||||||
|
name='cover', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='livelesson_covers', to='content.ImageObject', verbose_name='Обложка урока школы'), |
||||||
|
), |
||||||
|
migrations.AlterField( |
||||||
|
model_name='schoolschedule', |
||||||
|
name='cover', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schoolschedule_cover', to='content.ImageObject', verbose_name='Обложка дня'), |
||||||
|
), |
||||||
|
migrations.AlterField( |
||||||
|
model_name='schoolscheduleimage', |
||||||
|
name='img', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schoolschedule_images', to='content.ImageObject', verbose_name='Объект изображения'), |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-27 21:58 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('school', '0020_auto_20180824_2132'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AddField( |
||||||
|
model_name='schoolschedule', |
||||||
|
name='trial_lesson', |
||||||
|
field=models.URLField(blank=True, default=''), |
||||||
|
), |
||||||
|
] |
||||||
@ -1,78 +0,0 @@ |
|||||||
{% load static %} {% load thumbnail %} |
|
||||||
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle{% if not school_schedule.weekday in school_schedules_purchased %} disable{% endif %}"> |
|
||||||
<div class="timing__cell"> |
|
||||||
<div class="timing__info"> |
|
||||||
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}"> |
|
||||||
{{ school_schedule }} |
|
||||||
</div> |
|
||||||
{% if live_lesson %} |
|
||||||
<div class="timing__date">{{ live_lesson.date }}</div> |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
<div class="timing__buy"> |
|
||||||
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div> |
|
||||||
{% if school_schedule.weekday in school_schedules_purchased %} |
|
||||||
{% if live_lesson and live_lesson.title %} |
|
||||||
{% include './open_lesson.html' %} |
|
||||||
{% endif %} |
|
||||||
{% else %} |
|
||||||
{% include './day_pay_btn.html' %} |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
{% comment %} |
|
||||||
<!-- это нужно чтобы в попапе продления школы всегда знать какие дни выбраны(куплены) --> |
|
||||||
{% endcomment %} |
|
||||||
{% if school_schedule.weekday in school_schedules_purchased %} |
|
||||||
<span style="display: none;" data-purchased="{{ school_schedule.weekday }}"></span> |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
<div class="timing__cell"> |
|
||||||
<div class="timing__preview"> |
|
||||||
{% thumbnail live_lesson.cover.image "70x70" crop="center" as im %} |
|
||||||
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" /> |
|
||||||
{% empty %} |
|
||||||
<img class="timing__pic" src="{% static 'img/no_cover.png' %}" width="70px" height="70px" /> |
|
||||||
{% endthumbnail %} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="timing__cell"> |
|
||||||
<div class="timing__title">{{ school_schedule.title }}{% if live_lesson and live_lesson.title %}, |
|
||||||
<span class="bold">{{ live_lesson.title }}</span> |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
<div class="timing__content"> |
|
||||||
{% if live_lesson and live_lesson.short_description %} |
|
||||||
{{ live_lesson.short_description }} |
|
||||||
{% else %} |
|
||||||
{{ school_schedule.description }} |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
<div class="timing__more"> |
|
||||||
<div class="timing__head">Материалы</div> |
|
||||||
<div class="timing__row"> |
|
||||||
<div class="timing__text"> |
|
||||||
{{ school_schedule.materials }} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
{% if school_schedule.schoolschedule_images.exists %} |
|
||||||
<div class="timing__head">Результаты прошлых уроков</div> |
|
||||||
<div class="timing__works gallery"> |
|
||||||
{% for image in school_schedule.schoolschedule_images.all %} |
|
||||||
{% thumbnail image.img.image "48x48" crop="center" as im %} |
|
||||||
<a class="timing__work" href="{{ image.img.image.url }}"> |
|
||||||
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"/> |
|
||||||
</a> |
|
||||||
{% endthumbnail %} |
|
||||||
{% endfor %} |
|
||||||
</div> |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="timing__cell"> |
|
||||||
<button class="timing__toggle"> |
|
||||||
<svg class="icon icon-arrow-down"> |
|
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use> |
|
||||||
</svg> |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
@ -1,7 +1,5 @@ |
|||||||
<a |
<a |
||||||
{% if not user.is_authenticated %} |
data-popup=".js-popup-buy" |
||||||
data-popup=".js-popup-auth" |
|
||||||
{% endif %} |
|
||||||
class="timing__btn btn" |
class="timing__btn btn" |
||||||
href="{% url 'school-checkout' %}?weekdays={{ school_schedule.weekday }}&add_days=true" |
data-day="{{ school_schedule.weekday }}" |
||||||
>купить</a> |
>купить</a> |
||||||
|
|||||||
@ -0,0 +1,102 @@ |
|||||||
|
{% load static %} {% load thumbnail %} |
||||||
|
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle {% if print %}open{% endif %} {% if is_purchased and not school_schedule.weekday in school_schedules_purchased %} disable{% endif %}"> |
||||||
|
<div class="timing__cell timing__cell--info"> |
||||||
|
<div class="timing__info"> |
||||||
|
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}"> |
||||||
|
{{ school_schedule }} |
||||||
|
{% if not is_purchased and request.user_agent.is_mobile and school_schedule.trial_lesson %} |
||||||
|
<a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
{% if is_purchased and live_lesson %} |
||||||
|
<div class="timing__date">{% if request.user_agent.is_mobile %}{{ live_lesson.date|date:"j b" }}{% else %}{{ live_lesson.date|date:"j E" }}{% endif %}</div> |
||||||
|
{% endif %} |
||||||
|
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div> |
||||||
|
<div class="timing__buy"> |
||||||
|
{% if is_purchased and school_schedule.weekday in school_schedules_purchased %} |
||||||
|
{% if live_lesson and live_lesson.title %} |
||||||
|
{% include './open_lesson.html' %} |
||||||
|
{% endif %} |
||||||
|
{% else %} |
||||||
|
{% include './day_pay_btn.html' %} |
||||||
|
{% endif %} |
||||||
|
{% if not is_purchased and not request.user_agent.is_mobile and school_schedule.trial_lesson %} |
||||||
|
<a class="timing__trial-lesson js-video-modal" href="#" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% comment %} |
||||||
|
<!-- это нужно чтобы в попапе продления школы всегда знать какие дни выбраны(куплены) --> |
||||||
|
{% endcomment %} |
||||||
|
{% if school_schedule.weekday in school_schedules_purchased %} |
||||||
|
<span style="display: none;" data-purchased="{{ school_schedule.weekday }}"></span> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
<div style="display: flex; width: 100%;"> |
||||||
|
<div class="timing__cell timing__cell--preview"> |
||||||
|
<div class="timing__preview"> |
||||||
|
{% comment %} |
||||||
|
{% thumbnail live_lesson.cover.image "70x70" crop="center" as im %} |
||||||
|
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" /> |
||||||
|
{% empty %} |
||||||
|
{% endcomment %} |
||||||
|
<img class="timing__pic" |
||||||
|
src="{% if school_schedule.cover %}{{ school_schedule.cover.image_thumbnail.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %}" |
||||||
|
style="width: 70px; height: 70px;" /> |
||||||
|
{% comment %}{% endthumbnail %}{% endcomment %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="timing__cell timing__cell--content" {% if print %}style="flex: 1 0 0;"{% endif %}> |
||||||
|
<div class="timing__title">{{ school_schedule.title }}{% if live_lesson and live_lesson.title %}, |
||||||
|
<span class="bold">{{ live_lesson.title }}</span> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
<div class="timing__content"> |
||||||
|
{% comment %} |
||||||
|
{% if live_lesson and live_lesson.short_description %} |
||||||
|
{{ live_lesson.short_description }} |
||||||
|
{% else %} |
||||||
|
{% endcomment %} |
||||||
|
{{ school_schedule.description }} |
||||||
|
{% comment %}{% endif %}{% endcomment %} |
||||||
|
</div> |
||||||
|
<div class="timing__more"> |
||||||
|
<div class="timing__head">Материалы</div> |
||||||
|
<div class="timing__row"> |
||||||
|
{% if print %} |
||||||
|
<div class="timing__col" style="flex: 0 0 100%;"> |
||||||
|
<div class="timing__text"> |
||||||
|
{{ school_schedule.materials }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% else %} |
||||||
|
<div class="timing__text"> |
||||||
|
{{ school_schedule.materials }} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
{% if not print and school_schedule.schoolschedule_images.exists %} |
||||||
|
<div class="timing__head">Результаты прошлых уроков</div> |
||||||
|
<div class="timing__works gallery"> |
||||||
|
{% for image in school_schedule.schoolschedule_images.all %} |
||||||
|
{% thumbnail image.img.image "48x48" crop="center" as im %} |
||||||
|
<a class="timing__work" href="{{ image.img.image.url }}"> |
||||||
|
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"/> |
||||||
|
</a> |
||||||
|
{% endthumbnail %} |
||||||
|
{% endfor %} |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% if not print %} |
||||||
|
<div class="timing__cell timing__cell--toggle"> |
||||||
|
<button class="timing__toggle"> |
||||||
|
<svg class="icon icon-arrow-down"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use> |
||||||
|
</svg> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
@ -1,78 +0,0 @@ |
|||||||
{% load static %} {% load thumbnail %} |
|
||||||
<div class="timing__item{% if school_schedule.weekday > 5 %} timing__item_bg{% endif %} js-timing-item js-timing-toggle{% if not school_schedule.weekday in school_schedules_purchased %} disable{% endif %}"> |
|
||||||
<div class="timing__cell"> |
|
||||||
<div class="timing__info"> |
|
||||||
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}"> |
|
||||||
{{ school_schedule }} |
|
||||||
</div> |
|
||||||
{% if live_lesson %} |
|
||||||
<div class="timing__date">{{ live_lesson.date }}</div> |
|
||||||
{% endif %} |
|
||||||
<div class="timing__buy"> |
|
||||||
<div class="timing__time">{{ school_schedule.start_at }} (МСК)</div> |
|
||||||
{% if school_schedule.weekday in school_schedules_purchased %} |
|
||||||
{% if live_lesson and live_lesson.title %} |
|
||||||
{% include './open_lesson.html' %} |
|
||||||
{% endif %} |
|
||||||
{% else %} |
|
||||||
{% include './day_pay_btn.html' %} |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
{% comment %} |
|
||||||
<!-- это нужно чтобы в попапе продления школы всегда знать какие дни выбраны(куплены) --> |
|
||||||
{% endcomment %} |
|
||||||
{% if school_schedule.weekday in school_schedules_purchased %} |
|
||||||
<span style="display: none;" data-purchased="{{ school_schedule.weekday }}"></span> |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
<div class="timing__cell"> |
|
||||||
<div class="timing__preview"> |
|
||||||
{% thumbnail live_lesson.cover.image "70x70" crop="center" as im %} |
|
||||||
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" /> |
|
||||||
{% empty %} |
|
||||||
<img class="timing__pic" src="{% static 'img/no_cover.png' %}" width="70px" height="70px" /> |
|
||||||
{% endthumbnail %} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="timing__cell"> |
|
||||||
<div class="timing__title">{{ school_schedule.title }}{% if live_lesson and live_lesson.title %}, |
|
||||||
<span class="bold">{{ live_lesson.title }}</span> |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
<div class="timing__content"> |
|
||||||
{% if live_lesson and live_lesson.short_description %} |
|
||||||
{{ live_lesson.short_description }} |
|
||||||
{% else %} |
|
||||||
{{ school_schedule.description }} |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
<div class="timing__more"> |
|
||||||
<div class="timing__head">Материалы</div> |
|
||||||
<div class="timing__row"> |
|
||||||
<div class="timing__text"> |
|
||||||
{{ school_schedule.materials }} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
{% if school_schedule.schoolschedule_images.exists %} |
|
||||||
<div class="timing__head">Результаты прошлых уроков</div> |
|
||||||
<div class="timing__works gallery"> |
|
||||||
{% for image in school_schedule.schoolschedule_images.all %} |
|
||||||
{% thumbnail image.img.image "48x48" crop="center" as im %} |
|
||||||
<a class="timing__work" href="{{ image.img.image.url }}"> |
|
||||||
<img class="timing__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"/> |
|
||||||
</a> |
|
||||||
{% endthumbnail %} |
|
||||||
{% endfor %} |
|
||||||
</div> |
|
||||||
{% endif %} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="timing__cell"> |
|
||||||
<button class="timing__toggle"> |
|
||||||
<svg class="icon icon-arrow-down"> |
|
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#icon-arrow-down"></use> |
|
||||||
</svg> |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
@ -1,4 +1,4 @@ |
|||||||
<a |
<a |
||||||
class="timing__btn btn btn_light" |
class="timing__btn btn btn_light" |
||||||
href="{% url 'school:lesson-detail' live_lesson.id %}" |
href="{% url 'school:lesson-detail' live_lesson.id %}" |
||||||
>смотреть урок</a> |
>подробнее</a> |
||||||
|
|||||||
@ -0,0 +1,18 @@ |
|||||||
|
# Generated by Django 2.0.6 on 2018-08-22 12:11 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('user', '0022_user_instagram_hashtag'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AddField( |
||||||
|
model_name='user', |
||||||
|
name='trial_lesson', |
||||||
|
field=models.URLField(blank=True, default='', null=True), |
||||||
|
), |
||||||
|
] |
||||||
@ -0,0 +1,87 @@ |
|||||||
|
{% extends "templates/lilcity/index.html" %} {% load static %} {% load thumbnail %} {% block content %} |
||||||
|
<div class="section"> |
||||||
|
<div class="section__center center"> |
||||||
|
<div class="profile"> |
||||||
|
<div class="profile__ava ava"> |
||||||
|
{% thumbnail user.photo "120x120" crop="center" as im %} |
||||||
|
<img class="ava__pic" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" /> |
||||||
|
{% empty %} |
||||||
|
<img class="ava__pic" src="{% static 'img/user_default.jpg' %}" width="120px" height="120px" /> |
||||||
|
{% endthumbnail %} |
||||||
|
</div> |
||||||
|
<div class="profile__wrap"> |
||||||
|
<div class="profile__name">{{ user.get_full_name }}</div> |
||||||
|
<div class="profile__share share share_sm"> |
||||||
|
<div class="share__list"> |
||||||
|
{% if user.facebook %} |
||||||
|
<a class="share__item" href="{{ user.facebook }}"> |
||||||
|
<svg class="icon icon-share-facebook"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use> |
||||||
|
</svg> |
||||||
|
</a> |
||||||
|
{% endif %} {% if user.instagram %} |
||||||
|
<a class="share__item" href="{{ user.instagram }}"> |
||||||
|
<svg class="icon icon-share-instagram"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-instagram"></use> |
||||||
|
</svg> |
||||||
|
</a> |
||||||
|
{% endif %} {% if user.twitter %} |
||||||
|
<a class="share__item" href="{{ user.twitter }}"> |
||||||
|
<svg class="icon icon-share-twitter"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use> |
||||||
|
</svg> |
||||||
|
</a> |
||||||
|
{% endif %} {% if user.google %} |
||||||
|
<a class="share__item" href="{{ user.google }}"> |
||||||
|
<svg class="icon icon-share-google"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-google"></use> |
||||||
|
</svg> |
||||||
|
</a> |
||||||
|
{% endif %} {% if user.pinterest %} |
||||||
|
<a class="share__item" href="{{ user.pinterest }}"> |
||||||
|
<svg class="icon icon-share-pinterest"> |
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-pinterest"></use> |
||||||
|
</svg> |
||||||
|
</a> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="profile__content"> |
||||||
|
{% if user.about %} |
||||||
|
{{ user.about }} |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="section section_pink-light section_tabs"> |
||||||
|
<div class="section__center center"> |
||||||
|
<div class="tabs js-tabs"> |
||||||
|
<div class="tabs__nav"> |
||||||
|
<button class="tabs__btn js-tabs-btn {% if not owner %}active{% endif %}">ОПУБЛИКОВАННЫЕ |
||||||
|
<span class="mobile-hide">КУРСЫ</span> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="tabs__container"> |
||||||
|
<div class="tabs__item js-tabs-item" style="display: block;"> |
||||||
|
<div class="courses courses_scroll"> |
||||||
|
<div class="courses__list"> |
||||||
|
{% if published.exists %} |
||||||
|
{% include "course/course_items.html" with course_items=published %} |
||||||
|
{% else %} |
||||||
|
<div class="center center_xs"> |
||||||
|
<div class="done"> |
||||||
|
<div class="done__title title">Нет опубликованных курсов!</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{% endblock content %} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
{% load static %} |
||||||
|
<script> |
||||||
|
window.LIL_STORE = { |
||||||
|
staticUrl: '{% static "" %}', |
||||||
|
accessToken: '{{ request.user.auth_token }}', |
||||||
|
isMobile: {{ request.user_agent.is_mobile|yesno:"true,false" }}, |
||||||
|
defaultUserPhoto: "{% static 'img/user_default.jpg' %}", |
||||||
|
user: { |
||||||
|
id: '{{ request.user.id|default:'' }}', |
||||||
|
photo: '{% if request.user.photo %}{{ request.user.photo.url }}{% else %}{% static 'img/user_default.jpg' %}{% endif %}', |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -1,27 +1,14 @@ |
|||||||
{% load static %} |
{% load static %} |
||||||
|
|
||||||
|
|
||||||
<div class="share"> |
<div class="share"> |
||||||
<div class="share__title">Поделиться {% if livelesson or lesson %}уроком{% else %}курсом{% endif %}</div> |
<div class="share__title">Поделиться {% if share_object_name %}{{ share_object_name }}{% else %}{% if livelesson or lesson %}уроком{% else %}курсом{% endif %}{% endif %}</div> |
||||||
<div class="share__list likely"> |
<div class="share__list likely likely-big" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}"> |
||||||
<a class="share__item facebook" href="#" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}"> |
<div class="twitter"></div> |
||||||
<svg class="icon icon-share-facebook"> |
<div class="facebook"></div> |
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-facebook"></use> |
<div class="gplus"></div> |
||||||
</svg> |
<div class="vkontakte"></div> |
||||||
</a> |
<div class="telegram"></div> |
||||||
<a class="share__item twitter" href="#" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}"> |
<div class="pinterest"></div> |
||||||
<svg class="icon icon-share-twitter"> |
|
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-twitter"></use> |
|
||||||
</svg> |
|
||||||
</a> |
|
||||||
<a class="share__item gplus" href="#" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}"> |
|
||||||
<svg class="icon icon-share-google"> |
|
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-google"></use> |
|
||||||
</svg> |
|
||||||
</a> |
|
||||||
<a class="share__item pinterest" href="#" data-url="http://{{request.META.HTTP_HOST}}{{object.get_absolute_url}}"> |
|
||||||
<svg class="icon icon-share-pinterest"> |
|
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#icon-share-pinterest"></use> |
|
||||||
</svg> |
|
||||||
</a> |
|
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue