Merge branch 'feature/banner-countdown' into 'master'

Feature/banner countdown

See merge request lilschool/site!288
remotes/origin/feature/school-only-may-24-04-19
Danil 7 years ago
commit bf78199257
  1. 15
      api/v1/serializers/content.py
  2. 4
      api/v1/urls.py
  3. 10
      api/v1/views.py
  4. 5
      apps/auth/templates/auth/success-verification.html
  5. 6
      apps/content/admin.py
  6. 23
      apps/content/migrations/0024_auto_20190418_2253.py
  7. 17
      apps/content/migrations/0025_auto_20190419_0103.py
  8. 18
      apps/content/migrations/0026_banner_stretch_image.py
  9. 10
      apps/content/models.py
  10. 6
      project/context_processors.py
  11. 2
      project/settings.py
  12. 16
      project/templates/blocks/baner.html
  13. 38
      project/templates/blocks/banner.html
  14. 2
      project/templates/lilcity/index.html
  15. 4
      project/templates/lilcity/links.html
  16. 106
      web/src/components/blocks/Countdown.vue
  17. 11
      web/src/js/app.js
  18. 7
      web/src/js/modules/common.js
  19. 7
      web/src/js/utils.js
  20. 74
      web/src/sass/_common.sass

@ -3,7 +3,7 @@ from rest_framework import serializers
from django.conf import settings from django.conf import settings
from apps.content.models import ( from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video, Banner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, FAQ) Gallery, GalleryImage, ImageObject, FAQ)
from . import Base64ImageField from . import Base64ImageField
@ -24,11 +24,11 @@ BASE_CONTENT_FIELDS = (
) )
class BanerSerializer(serializers.ModelSerializer): class BannerSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField() image = serializers.SerializerMethodField()
class Meta: class Meta:
model = Baner model = Banner
fields = ( fields = (
'id', 'id',
'text', 'text',
@ -36,6 +36,9 @@ class BanerSerializer(serializers.ModelSerializer):
'url', 'url',
'image', 'image',
'use', 'use',
'color',
'stretch_image',
'future_date',
'created_at', 'created_at',
'update_at', 'update_at',
) )
@ -46,9 +49,9 @@ class BanerSerializer(serializers.ModelSerializer):
'update_at', 'update_at',
) )
def get_image(self, baner): def get_image(self, banner):
if baner.image: if banner.image:
return 'http://' + settings.MAIN_HOST + '/' + baner.image.url return 'http://' + settings.MAIN_HOST + '/' + banner.image.url
else: else:
return None return None

@ -9,7 +9,7 @@ from drf_yasg import openapi
from .auth import ObtainToken, ObtainTempToken from .auth import ObtainToken, ObtainTempToken
from .views import ( from .views import (
AuthorBalanceViewSet, AuthorRequestViewSet, AuthorBalanceViewSet, AuthorRequestViewSet,
BanerViewSet, ConfigViewSet, CategoryViewSet, BannerViewSet, ConfigViewSet, CategoryViewSet,
CourseViewSet, CommentViewSet, CourseViewSet, CommentViewSet,
MaterialViewSet, LikeViewSet, MaterialViewSet, LikeViewSet,
ImageViewSet, TextViewSet, ImageViewSet, TextViewSet,
@ -24,7 +24,7 @@ from .views import (
router = DefaultRouter() router = DefaultRouter()
router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests') router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests')
router.register(r'author-balance', AuthorBalanceViewSet, base_name='author-balance') router.register(r'author-balance', AuthorBalanceViewSet, base_name='author-balance')
router.register(r'baners', BanerViewSet, base_name='baners') router.register(r'baners', BannerViewSet, base_name='banners')
router.register(r'categories', CategoryViewSet, base_name='categories') router.register(r'categories', CategoryViewSet, base_name='categories')
router.register(r'courses', CourseViewSet, base_name='courses') router.register(r'courses', CourseViewSet, base_name='courses')
router.register(r'comments', CommentViewSet, base_name='comments') router.register(r'comments', CommentViewSet, base_name='comments')

@ -23,7 +23,7 @@ from .serializers.course import (
LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer, LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer,
LiveLessonCommentSerializer,) LiveLessonCommentSerializer,)
from .serializers.content import ( from .serializers.content import (
BanerSerializer, BannerSerializer,
ImageSerializer, ImageCreateSerializer, ImageSerializer, ImageCreateSerializer,
TextSerializer, TextCreateSerializer, TextSerializer, TextCreateSerializer,
ImageTextSerializer, ImageTextCreateSerializer, ImageTextSerializer, ImageTextCreateSerializer,
@ -63,7 +63,7 @@ from apps.course.models import (
LiveLessonComment) LiveLessonComment)
from apps.config.models import Config from apps.config.models import Config
from apps.content.models import ( from apps.content.models import (
Baner, Image, Text, ImageText, Video, Banner, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
Contest, ContestWork, FAQ) Contest, ContestWork, FAQ)
from apps.payment.models import ( from apps.payment.models import (
@ -132,9 +132,9 @@ class AuthorBalanceUsersViewSet(views.APIView):
return Response(UserSerializer(users, many=True).data) return Response(UserSerializer(users, many=True).data)
class BanerViewSet(ExtendedModelViewSet): class BannerViewSet(ExtendedModelViewSet):
queryset = Baner.objects.all() queryset = Banner.objects.all()
serializer_class = BanerSerializer serializer_class = BannerSerializer
permission_classes = (IsAdmin,) permission_classes = (IsAdmin,)
filter_fields = ('use',) filter_fields = ('use',)
ordering_fields = ('created_at', 'update_at',) ordering_fields = ('created_at', 'update_at',)

@ -6,8 +6,9 @@
<div class="done"> <div class="done">
<div class="done__title title">Email подтверждён!</div> <div class="done__title title">Email подтверждён!</div>
<div class="done__content"> <div class="done__content">
<p>Через несколько секунд Вас перенаправят на главную страницу.</p> <p>
<p>Или нажмите кнопку ниже.</p> Получите 10 творческих заданий по <a href="https://drive.google.com/drive/folders/1IjUqzzMxCQFvsQl6Qt91FLdFaaMMChDO">ссылке</a>
</p>
</div> </div>
<div class="done__foot"> <div class="done__foot">
<a class="done__btn btn btn_md btn_stroke" href="/">ПЕРЕЙТИ НА ГЛАВНУЮ</a> <a class="done__btn btn btn_md btn_stroke" href="/">ПЕРЕЙТИ НА ГЛАВНУЮ</a>

@ -6,14 +6,14 @@ from polymorphic.admin import (
) )
from apps.content.models import ( from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video, Banner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
Contest, ContestWork, FAQ, Contest, ContestWork, FAQ,
) )
@admin.register(Baner) @admin.register(Banner)
class BanerAdmin(admin.ModelAdmin): class BannerAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'text', 'text',
'button_text', 'button_text',

@ -0,0 +1,23 @@
# Generated by Django 2.0.7 on 2019-04-18 22:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0023_faq'),
]
operations = [
migrations.AddField(
model_name='baner',
name='color',
field=models.CharField(blank=True, default='', max_length=7),
),
migrations.AddField(
model_name='baner',
name='future_date',
field=models.DateTimeField(blank=True, null=True),
),
]

@ -0,0 +1,17 @@
# Generated by Django 2.0.7 on 2019-04-19 01:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('content', '0024_auto_20190418_2253'),
]
operations = [
migrations.RenameModel(
old_name='Baner',
new_name='Banner',
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.7 on 2019-04-19 15:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0025_auto_20190419_0103'),
]
operations = [
migrations.AddField(
model_name='banner',
name='stretch_image',
field=models.BooleanField(default=True),
),
]

@ -132,13 +132,15 @@ class GalleryImage(models.Model):
ordering = ('-created_at',) ordering = ('-created_at',)
class Baner(models.Model): class Banner(models.Model):
text = models.TextField() text = models.TextField()
button_text = models.CharField(max_length=50) button_text = models.CharField(max_length=50)
url = models.URLField() url = models.URLField()
image = models.ImageField() image = models.ImageField()
use = models.BooleanField(default=False) use = models.BooleanField(default=False)
color = models.CharField(max_length=7, blank=True, default='')
stretch_image = models.BooleanField(default=True)
future_date = models.DateTimeField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True) update_at = models.DateTimeField(auto_now=True)
@ -149,7 +151,7 @@ class Baner(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.use: if self.use:
Baner.objects.filter(use=True).update(use=False) Banner.objects.filter(use=True).update(use=False)
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@ -168,7 +170,7 @@ class Contest(models.Model):
date_start = models.DateField('Дата начала', null=True, blank=True) date_start = models.DateField('Дата начала', null=True, blank=True)
date_end = models.DateField('Дата окончания', null=True, blank=True) date_end = models.DateField('Дата окончания', null=True, blank=True)
active = models.BooleanField(default=True) active = models.BooleanField(default=True)
# TODO? baner # TODO? banner
@property @property
def finished(self): def finished(self):

@ -4,7 +4,7 @@ from paymentwall.pingback import Pingback
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.conf import settings from django.conf import settings
from apps.config.models import Config from apps.config.models import Config
from apps.content.models import Baner from apps.content.models import Banner
from apps.payment.models import SchoolPayment from apps.payment.models import SchoolPayment
User = get_user_model() User = get_user_model()
@ -14,8 +14,8 @@ def config(request):
return {"config": Config.load()} return {"config": Config.load()}
def baner(request): def banner(request):
return {'baner': Baner.objects.filter(use=True).first()} return {'banner': Banner.objects.filter(use=True).first()}
def school_purchased(request): def school_purchased(request):

@ -95,7 +95,7 @@ TEMPLATES = [
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'project.context_processors.config', 'project.context_processors.config',
'project.context_processors.baner', 'project.context_processors.banner',
'project.context_processors.school_purchased', 'project.context_processors.school_purchased',
'project.context_processors.referrer', 'project.context_processors.referrer',
'project.context_processors.settings', 'project.context_processors.settings',

@ -1,16 +0,0 @@
{% if baner %}
<div class="banner" data-banner="{{baner.id}}"
style="{% if not request.user_agent.is_mobile %}background-image: url({{ baner.image.url }});{% endif %} display: none;">
{% if request.user_agent.is_mobile %}
<a href="{{ baner.url }}" class="banner__image-wrap">
<img class="banner__image" src="{{ baner.image.url }}" />
</a>
{% else %}
<div class="banner__content">
<span class="banner__text">{{ baner.text }}</span>
<a href="{{ baner.url }}" class="banner__link">{{ baner.button_text }}</a>
</div>
{% endif %}
<a href="#" class="banner__hide">Скрыть баннер</a>
</div>
{% endif %}

@ -0,0 +1,38 @@
{% if banner %}
<div class="banner {% if banner.color %}banner_colored{% endif %} {% if banner.future_date %}banner_countdown{% endif %}"
data-banner="{{banner.id}}"
style="
{% if not request.user_agent.is_mobile %}
background-image: url({{ banner.image.url }});
{% endif %}
{% if banner.stretch_image %}
background-position: center;
background-size: cover;
{% else %}
background-position: center bottom;
{% endif %}
display: none;
background-color: {{ banner.color|default:'white' }};">
{% if request.user_agent.is_mobile %}
<a href="{{ banner.url }}" class="banner__image-wrap">
<img class="banner__image" src="{{ banner.image.url }}" />
</a>
{% else %}
<div class="banner__content">
<div class="banner__content-center">
<div class="banner__text-column">
<div class="banner__text">{{ banner.text }}</div>
<a href="{{ banner.url }}" class="banner__link">{{ banner.button_text }}</a>
</div>
{% if banner.future_date %}
<div class="banner__countdown-column">
<div class="banner__countdown-title">До конца акции осталось</div>
<countdown date="{{ banner.future_date|date:'Y-m-d H:i:s' }}"></countdown>
</div>
{% endif %}
</div>
</div>
{% endif %}
<a href="#" class="banner__hide">Скрыть баннер</a>
</div>
{% endif %}

@ -32,7 +32,7 @@
{% block layer_body %} {% block layer_body %}
{% include "templates/blocks/social.html" %} {% include "templates/blocks/social.html" %}
{% include "templates/blocks/baner.html" %} {% include "templates/blocks/banner.html" %}
<div class="outer js-outer"> <div class="outer js-outer">
{% include "templates/blocks/header.html" %} {% include "templates/blocks/header.html" %}
<div id="lilcity-vue-app" class="container"> <div id="lilcity-vue-app" class="container">

@ -48,6 +48,10 @@
href="{% url 'course' 'kak-stat-illiustratorom' %}?utm_source=posadochnaya_stranica&utm_medium=Kurs_sashaKru&utm_campaign=Kurs_sashaKru"> href="{% url 'course' 'kak-stat-illiustratorom' %}?utm_source=posadochnaya_stranica&utm_medium=Kurs_sashaKru&utm_campaign=Kurs_sashaKru">
Курс иллюстрации от SashaKru Курс иллюстрации от SashaKru
</a> </a>
<a class="btn" style="margin: 10px 0;"
href="https://docs.google.com/forms/d/e/1FAIpQLScM8snMKc6lCRJ7xQ1eJ3SNwckB3EVssHmuXdp13OJzgK49JQ/viewform?usp=sf_link">
Пройти опрос
</a>
</div> </div>
</div> </div>
</div> </div>

@ -0,0 +1,106 @@
<template>
<div class="banner__countdown" style="opacity: 0;">
<div>
<div class="banner__countdown-nums">
<div>{{ days[0] }}</div>
<div>{{ days[1] }}</div>
</div>
<div class="banner__countdown-descr">
{{ rupluralize(+days, ['День', 'Дня', 'Дней'], false) }}
</div>
</div>
<div class="banner__countdown-delim"></div>
<div>
<div class="banner__countdown-nums">
<div>{{ hours[0] }}</div>
<div>{{ hours[1] }}</div>
</div>
<div class="banner__countdown-descr">
{{ rupluralize(+hours, ['Час', 'Часа', 'Часов'], false) }}
</div>
</div>
<div class="banner__countdown-delim">:</div>
<div>
<div class="banner__countdown-nums">
<div>{{ minutes[0] }}</div>
<div>{{ minutes[1] }}</div>
</div>
<div class="banner__countdown-descr">
{{ rupluralize(+minutes, ['Минута', 'Минуты', 'Минут'], false) }}
</div>
</div>
<div class="banner__countdown-delim">:</div>
<div>
<div class="banner__countdown-nums">
<div>{{ seconds[0] }}</div>
<div>{{ seconds[1] }}</div>
</div>
<div class="banner__countdown-descr">
{{ rupluralize(+seconds, ['Секунда', 'Секунды', 'Секунд'], false) }}
</div>
</div>
</div>
</template>
<script>
import moment from 'moment';
import {rupluralize} from '../../js/utils'
export default {
name: "Countdown",
props: ['date', 'format'],
data: () => ({
days: '00',
hours: '00',
minutes: '00',
seconds: '00',
i: 0,
rupluralize: rupluralize,
}),
beforeDestroy(){
if ( window['cancelAnimationFrame'] ) {
window.cancelAnimationFrame(this.frame);
}
},
mounted() {
if ( window['requestAnimationFrame'] ) {
this.countdown = moment(this.date, this.format).toDate();
this.update();
}
},
methods: {
zerofill(value) {
return (value < 10 && value > -1 ? '0' : '') + value;
},
update() {
this.frame = window.requestAnimationFrame(this.update.bind(this));
if(this.i++ % 10 || this.$el.offsetParent === null) {
return;
}
this.$el.style.opacity = 1;
const t = this.countdown - (new Date());
if(t < 0){
this.days = this.hours = this.minutes = this.seconds = '00';
return;
}
this.days = this.zerofill(Math.floor(t / (1000 * 60 * 60 * 24)));
this.hours = this.zerofill(Math.floor((t / (1000 * 60 * 60)) % 24));
this.minutes = this.zerofill(Math.floor((t / 1000 / 60) % 60));
this.seconds = this.zerofill(Math.floor((t / 1000) % 60));
}
}
}
</script>
<style scoped>
</style>

@ -27,6 +27,7 @@ import DatePicker from 'vuejs-datepicker'
import Comments from '../components/Comments'; import Comments from '../components/Comments';
import Likes from '../components/blocks/Likes.vue'; import Likes from '../components/blocks/Likes.vue';
import FAQ from '../components/FAQ.vue'; import FAQ from '../components/FAQ.vue';
import Countdown from '../components/blocks/Countdown.vue';
Vue.use(Vuelidate); Vue.use(Vuelidate);
Vue.use(VueAutosize); Vue.use(VueAutosize);
@ -54,3 +55,13 @@ const app = new Vue({
}, },
components: components components: components
}); });
const bannerApp = new Vue({
el: '.banner',
data() {
return {
store: window.LIL_STORE,
}
},
components: {'countdown': Countdown,}
});

@ -5,6 +5,7 @@ import SmoothScroll from 'smooth-scroll/dist/js/smooth-scroll';
import baguetteBox from 'baguettebox.js' import baguetteBox from 'baguettebox.js'
import createHistory from 'history/createBrowserHistory' import createHistory from 'history/createBrowserHistory'
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import moment from 'moment'
window.$ = window.jQuery = jQuery = $; window.$ = window.jQuery = jQuery = $;
window.Inputmask = Inputmask; window.Inputmask = Inputmask;
@ -32,6 +33,7 @@ $(document).ready(function () {
//===========BANNERS=============== //===========BANNERS===============
const $banner = $('[data-banner]'); const $banner = $('[data-banner]');
const bannerId = $banner.data('banner') + ''; const bannerId = $banner.data('banner') + '';
const futureDate = $banner.data('future-date') + '';
if(Cookies.get('hide_banner') !== bannerId){ if(Cookies.get('hide_banner') !== bannerId){
$banner.show(); $banner.show();
} }
@ -43,6 +45,11 @@ $(document).ready(function () {
Cookies.set('hide_banner', bannerId); Cookies.set('hide_banner', bannerId);
}); });
if(futureDate){
}
$banner.find('.banner__countdown')
//===========REVIEWS=============== //===========REVIEWS===============
if(window.LIL_STORE.isIndexPage){ if(window.LIL_STORE.isIndexPage){
const $reviews = $('.reviews'); const $reviews = $('.reviews');

@ -1,7 +1,10 @@
export const rupluralize = (value, args) => { export const rupluralize = (value, args, addValue=true) => {
let digit = Math.trunc(value) + ''; let digit = Math.trunc(value) + '';
digit = digit[digit.length - 1]; digit = digit[digit.length - 1];
return value + ' ' + args[digit == '1' ? 0 : ('234'.search(digit) > -1 ? 1 : 2)]; return (addValue ? value + ' ' : '') +
args[(+value > 10 && +value < 20)
? 2
: (digit == '1' ? 0 : ('234'.search(digit) > -1 ? 1 : 2))];
} }
export const loadScript = (url, onload) => { export const loadScript = (url, onload) => {

@ -4388,10 +4388,7 @@ a
height: 140px height: 140px
text-align: center text-align: center
color: #fff color: #fff
background-color: white
background-repeat: no-repeat background-repeat: no-repeat
background-position: center
background-size: cover
+m +m
height: auto height: auto
&__image-wrap &__image-wrap
@ -4405,15 +4402,33 @@ a
+m +m
width: 100% width: 100%
display: block display: block
margin: 0 auto
&_colored &__image
+m
max-width: 100%
width: auto
opactiy: 0.8
&__content &__content
position: absolute position: absolute
width: 100% width: 100%
margin-top: 50px margin-top: 50px
+m +m
margin-top: -110px margin-top: -110px
&_countdown &__content
margin-top: 15px
&__content-center
width: 1024px
margin: 0 auto
display: flex
padding: 0 40px
&__text &__text
font-size: 30px font-size: 30px
text-shadow: 0px 0px 3px rgba(0, 0, 0, 1) text-shadow: 0px 0px 3px rgba(0, 0, 0, 1)
&_countdown &__text
font-size: 22px
text-shadow: none
color: black
width: 300px
&__link &__link
display: block display: block
color: #fff color: #fff
@ -4422,6 +4437,20 @@ a
margin-top: 20px margin-top: 20px
+m +m
margin-top: 0px margin-top: 0px
&_countdown &__link
font-size: 15px
color: black
text-shadow: none
background: white
font-weight: bold
padding: 7px 20px
border-radius: 20px
border: 1px solid #ddd
text-align: center
margin-top: 10px
display: inline-block
&_countdown &__link:hover
background: #ddd
&__hide &__hide
position: absolute position: absolute
right: 5px right: 5px
@ -4434,6 +4463,45 @@ a
opacity: 0.3 opacity: 0.3
+m +m
//margin-top: 0px //margin-top: 0px
&__text-column
flex: 1
&_countdown &__text-column
text-align: left
&__countdown-column
padding: 0 20px
+m
border-radius: 3px
background: rgba(255, 255, 255, 0.3)
&__countdown-title
color: black
font-size: 15px
margin-bottom: 5px
&__countdown
display: flex
color: black
transition: 0.5s opacity
> div
font-size: 27px
display: flex
flex-direction: column
&-nums
display: flex
& > div
margin: 1px
width: 27px
border-radius: 5px
background: #000000ba
text-align: center
padding-top: 2px
color: white
&-delim
width: 10px
color: black
&-descr
text-align: center
font-size: 11px
margin-top: 3px
.anchor .anchor
padding-top: 100px padding-top: 100px

Loading…
Cancel
Save