remotes/origin/hotfix/LIL-661
gzbender 8 years ago
parent 876cefab66
commit 6630ec34ee
  1. 14
      api/v1/serializers/contest.py
  2. 2
      api/v1/urls.py
  3. 13
      api/v1/views.py
  4. 8
      apps/content/models.py
  5. 3
      apps/content/templates/content/blocks/contest_work.html
  6. 10
      apps/content/templates/content/contest.html
  7. 64
      apps/content/templates/content/contest_work.html
  8. 8
      apps/content/views.py
  9. 3
      project/urls.py
  10. 34
      web/src/components/ContestWorks.vue
  11. 8
      web/src/components/UploadContestWork.vue
  12. 75
      web/src/components/blocks/ContestWork.vue
  13. 4
      web/src/js/app.js
  14. 28
      web/src/sass/_common.sass

@ -9,6 +9,7 @@ from apps.content.models import (Contest, ContestWork)
class ContestSerializer(serializers.ModelSerializer): class ContestSerializer(serializers.ModelSerializer):
cover = ImageObjectSerializer() cover = ImageObjectSerializer()
content = ContentSerializer(many=True) content = ContentSerializer(many=True)
class Meta: class Meta:
model = Contest model = Contest
fields = '__all__' fields = '__all__'
@ -19,6 +20,7 @@ class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer)
child=ContentCreateSerializer(), child=ContentCreateSerializer(),
required=False, required=False,
) )
class Meta: class Meta:
model = Contest model = Contest
fields = '__all__' fields = '__all__'
@ -40,6 +42,18 @@ class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer)
class ContestWorkSerializer(serializers.ModelSerializer): class ContestWorkSerializer(serializers.ModelSerializer):
image = ImageObjectSerializer()
class Meta:
model = ContestWork
fields = ['id', 'user', 'contest', 'image', 'child_full_name', 'age',
'created_at', 'likes', 'img_width', 'img_height']
class ContestWorkCreateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ContestWork model = ContestWork
fields = '__all__' fields = '__all__'
def to_representation(self, instance):
return ContestWorkSerializer(instance=instance, context=self.context).to_representation(instance)

@ -45,7 +45,7 @@ router.register(r'school-schedules', SchoolScheduleViewSet, base_name='school-sc
router.register(r'users', UserViewSet, base_name='users') router.register(r'users', UserViewSet, base_name='users')
router.register(r'contests', ContestViewSet, base_name='contests') router.register(r'contests', ContestViewSet, base_name='contests')
router.register(r'contest_works', ContestWorkViewSet, base_name='contest_works') router.register(r'contest-works', ContestWorkViewSet, base_name='contest_works')
# router.register(r'configs', ConfigViewSet, base_name='configs') # router.register(r'configs', ConfigViewSet, base_name='configs')

@ -39,7 +39,9 @@ from .serializers.user import (
AuthorRequestSerializer, AuthorRequestSerializer,
UserSerializer, UserPhotoSerializer, UserSerializer, UserPhotoSerializer,
) )
from .serializers.contest import ContestCreateSerializer, ContestSerializer, ContestWorkSerializer from .serializers.contest import (
ContestCreateSerializer, ContestSerializer, ContestWorkSerializer, ContestWorkCreateSerializer
)
from .permissions import ( from .permissions import (
IsAdmin, IsAdminOrIsSelf, IsAdmin, IsAdminOrIsSelf,
@ -446,5 +448,10 @@ class ContestViewSet(ExtendedModelViewSet):
class ContestWorkViewSet(ExtendedModelViewSet): class ContestWorkViewSet(ExtendedModelViewSet):
queryset = ContestWork.objects.all() queryset = ContestWork.objects.order_by('-created_at')
serializer_class = ContestWorkSerializer serializer_class = ContestWorkCreateSerializer
serializer_class_map = {
'list': ContestWorkSerializer,
'retrieve': ContestWorkSerializer,
}
filter_fields = ('contest',)

@ -179,3 +179,11 @@ class ContestWork(models.Model):
age = models.SmallIntegerField() age = models.SmallIntegerField()
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
likes = models.ManyToManyField('course.Like', blank=True) likes = models.ManyToManyField('course.Like', blank=True)
@property
def img_width(self):
return self.image.image.width if self.image and self.image.image else None
@property
def img_height(self):
return self.image.image.height if self.image and self.image.image else None

@ -12,7 +12,7 @@
{{ contest.description }} {{ contest.description }}
</div> </div>
<div class="main__actions"> <div class="main__actions">
<a class="main__btn btn btn_white" href="#" data-show-upload-contest-work>Загрузить свою работу</a> <a class="main__btn btn" href="#" data-show-upload-contest-work>Загрузить свою работу</a>
</div> </div>
</div> </div>
</div> </div>
@ -31,13 +31,7 @@
</a> </a>
<div class="text"> <div class="text">
</div> </div>
<div class="gallery"> <contest-works contest-id="{{ contest.id }}"></contest-works>
<div class="gallery__grid">
{% for contest_work in contest_works %}
{% include 'content/blocks/contest_work.html' %}
{% endfor %}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -0,0 +1,64 @@
{% extends "templates/lilcity/index.html" %}
{% load static %}
{% block title %}{{ contest_work.child_full_name }}, {{ contest_work.age }} лет{% endblock title %}
{% block ogimage %}http://{{request.META.HTTP_HOST}}{{ contest_work.image.url }}{% endblock ogimage %}
{% 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.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">Вернуться к&nbsp;галлерее</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>
<div>{{ contest_work.child_full_name }}</div>
<div class="contest-work__age">{{ contest_work.age }} лет</div>
</div>
<div class="contest-work__likes">
{{ contest_work.likes.count }}<svg class="icon">
<use v-bind="{'xlink:href': $root.store.staticUrl + 'img/sprite.svg' + '#icon-like' }"></use>
</svg>
</div>
</div>
</div>
</div>
<div class="section" style="padding: 0;">
<div class="section__center center center_sm">
<div class="go">
<a class="go__item" href="{% url 'contest' contest_work.contest.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>
<a class="go__item" href="{% url 'contest' contest_work.contest.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>
</div>
</div>
</div>
<div class="section">
</div>
{% endblock content %}

@ -3,7 +3,7 @@ from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import TemplateView, DetailView from django.views.generic import TemplateView, DetailView
from apps.content.models import Contest from apps.content.models import Contest, ContestWork
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
@ -28,3 +28,9 @@ class ContestView(DetailView):
model = Contest model = Contest
context_object_name = 'contest' context_object_name = 'contest'
template_name = 'content/contest.html' template_name = 'content/contest.html'
class ContestWorkView(DetailView):
model = ContestWork
context_object_name = 'contest_work'
template_name = 'content/contest_work.html'

@ -18,7 +18,7 @@ from django.contrib import admin
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.urls import path, include from django.urls import path, include
from apps.content.views import ContestEditView, ContestView from apps.content.views import ContestEditView, ContestView, ContestWorkView
from apps.course.views import ( from apps.course.views import (
CoursesView, likes, coursecomment, CoursesView, likes, coursecomment,
CourseView, LessonView, SearchView, CourseView, LessonView, SearchView,
@ -86,6 +86,7 @@ urlpatterns = [
path('contest/create', ContestEditView.as_view(), name='contest_create'), path('contest/create', ContestEditView.as_view(), name='contest_create'),
path('contest/<int:pk>/edit', ContestEditView.as_view(), name='contest_edit'), path('contest/<int:pk>/edit', ContestEditView.as_view(), name='contest_edit'),
path('contest/<int:pk>/', ContestView.as_view(), name='contest'), path('contest/<int:pk>/', ContestView.as_view(), name='contest'),
path('contest-work/<int:pk>/', ContestWorkView.as_view(), name='contest_work'),
] ]

@ -1,30 +1,40 @@
<template> <template>
<div class="contest-works"></div> <div class="contest-works">
<contest-work v-for="contestWork in contestWorks" :contest-work="contestWork"></contest-work>
</div>
</template> </template>
<script> <script>
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
import ContestWork from './blocks/ContestWork'; import ContestWork from "./blocks/ContestWork.vue";
export default { export default {
name: "contest-work", name: "contest-works",
prop: ['contestId'], props: ['contestId'],
data(){ data(){
return { return {
contestWorks: [],
}; };
}, },
mounted() { mounted() {
api.getContestWorks(this.contestId).then((response) => { this.load();
},
}); methods: {
} load() {
api.get(`/api/v1/contest-works/?contest=${this.contestId}`)
.then((response) => {
this.contestWorks = response.data.results;
});
}
},
components: {ContestWork},
} }
</script> </script>
<style> <style>
.contest-works::after { .contest-works {
content ' ' column-width: 300px;
flex-grow 99999999 column-gap: 20px;
text-align: left;
} }
</style> </style>

@ -1,6 +1,6 @@
<template> <template>
<div ref="popup" class="upload-contest-work popup"> <div ref="popup" class="upload-contest-work popup" @click.prevent="hide">
<div class="popup__wrap popup__wrap_md"> <div class="popup__wrap popup__wrap_md" @click.stop>
<button class="popup__close" @click.prevent="hide"> <button class="popup__close" @click.prevent="hide">
<svg class="icon icon-close"> <svg class="icon icon-close">
<use v-bind="{'xlink:href': $root.store.staticUrl + 'img/sprite.svg' + '#icon-close' }"></use> <use v-bind="{'xlink:href': $root.store.staticUrl + 'img/sprite.svg' + '#icon-close' }"></use>
@ -33,7 +33,7 @@
</div> </div>
<div class="field" style="text-align: center;"> <div class="field" style="text-align: center;">
<button class="btn btn_light" tabindex="3" @click="save">Отправить на конкурс</button> <button class="btn" tabindex="3" @click="save">Отправить на конкурс</button>
</div> </div>
</form> </form>
</div> </div>
@ -131,7 +131,7 @@
let data = this.contestWork; let data = this.contestWork;
data.contest = this.contestId; data.contest = this.contestId;
data.user = this.$root.store.user.id; data.user = this.$root.store.user.id;
const request = api.post(`/api/v1/contest_works/`, data, { const request = api.post(`/api/v1/contest-works/`, data, {
headers: { headers: {
'Authorization': `Token ${this.$root.store.accessToken}`, 'Authorization': `Token ${this.$root.store.accessToken}`,
} }

@ -1,38 +1,63 @@
<template> <template>
<div class="contest-work" :style="style"> <a :href="`/contest-work/${contestWork.id}/`" class="contest-work-item">
<i :style="{paddingBottom: imgHeight / imgWidth * 100 + '%'}"></i> <img class="contest-work-item__img" :src="contestWork.image.image" />
<img class="contest-work__img" :src="contestWork.image" /> <div class="contest-work-item__info">
<div>
<div>{{ contestWork.child_full_name }}</div>
<div class="contest-work-item__age">{{ contestWork.age }} лет</div>
</div>
<div class="contest-work-item__likes">
{{ contestWork.likes.length }}<svg class="icon">
<use v-bind="{'xlink:href': $root.store.staticUrl + 'img/sprite.svg' + '#icon-like' }"></use>
</svg>
</div>
</div> </div>
</a>
</template> </template>
<script> <script>
export default { export default {
name: "contest-work", name: "contest-work",
props: ['contestWork'], props: ['contestWork'],
computed: {
imgHeight() {
return contestWork.imgHeight;
},
imgWidth() {
return contestWork.imgWidth;
},
style() {
const aspectRatio = this.imgWidth / this.imgHeight;
return {
width: `${this.averageFileCardHeight * aspectRatio}px`,
flexGrow: this.averageFileCardHeight * aspectRatio,
};
}
}
} }
</script> </script>
<style> <style lang="scss">
.contest-work__img { .contest-work-item {
position: absolute; break-inside: avoid;
max-height 100%; border-radius: 8px;
max-width 100%; overflow: hidden;
width auto; margin-bottom: 20px;
transition: opacity .4s ease-in-out;
text-transform: uppercase;
font-weight: bold;
color: black;
border: 1px solid #ececec;
display: block;
}
.contest-work-item__img {
width: 100%;
height: auto;
}
.contest-works:hover .contest-work-item:not(:hover) {
opacity: 0.4;
}
.contest-work-item__info {
display: flex;
padding: 5px 10px;
}
.contest-work-item__age {
color: #919191;
}
.contest-work-item__likes {
flex: 70px;
text-align: right;
.icon {
width: 15px;
height: 15px;
margin-left: 5px;
margin-bottom: -3px;
}
} }
</style> </style>

@ -21,6 +21,7 @@ import "../sass/app.sass";
import Vue from 'vue'; import Vue from 'vue';
import Vuelidate from 'vuelidate'; import Vuelidate from 'vuelidate';
import UploadContestWork from '../components/UploadContestWork.vue'; import UploadContestWork from '../components/UploadContestWork.vue';
import ContestWorks from '../components/ContestWorks.vue';
Vue.use(Vuelidate); Vue.use(Vuelidate);
@ -37,6 +38,7 @@ const app = new Vue({
} }
}, },
components: { components: {
'upload-contest-work': UploadContestWork, UploadContestWork,
ContestWorks,
} }
}); });

@ -4097,3 +4097,31 @@ a
.anchor .anchor
padding-top: 100px padding-top: 100px
margin-top: -100px margin-top: -100px
.contest-work
text-transform: uppercase;
font-weight: bold;
&__img-wrap
width: 100%;
text-align: center;
&__img
max-width: 100%;
&__info
display: flex;
padding: 5px 10px;
&__age
color: #919191;
&__likes
flex: 70px;
text-align: right;
&__likes .icon
width: 15px;
height: 15px;
margin-left: 5px;
margin-bottom: -3px;

Loading…
Cancel
Save