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):
cover = ImageObjectSerializer()
content = ContentSerializer(many=True)
class Meta:
model = Contest
fields = '__all__'
@ -19,6 +20,7 @@ class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer)
child=ContentCreateSerializer(),
required=False,
)
class Meta:
model = Contest
fields = '__all__'
@ -40,6 +42,18 @@ class ContestCreateSerializer(DispatchContentMixin, 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:
model = ContestWork
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'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')

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

@ -179,3 +179,11 @@ class ContestWork(models.Model):
age = models.SmallIntegerField()
created_at = models.DateTimeField(auto_now_add=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 }}
</div>
<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>
@ -31,13 +31,7 @@
</a>
<div class="text">
</div>
<div class="gallery">
<div class="gallery__grid">
{% for contest_work in contest_works %}
{% include 'content/blocks/contest_work.html' %}
{% endfor %}
</div>
</div>
<contest-works contest-id="{{ contest.id }}"></contest-works>
</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.views.generic import TemplateView, DetailView
from apps.content.models import Contest
from apps.content.models import Contest, ContestWork
@method_decorator(login_required, name='dispatch')
@ -28,3 +28,9 @@ class ContestView(DetailView):
model = Contest
context_object_name = 'contest'
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.urls import path, include
from apps.content.views import ContestEditView, ContestView
from apps.content.views import ContestEditView, ContestView, ContestWorkView
from apps.course.views import (
CoursesView, likes, coursecomment,
CourseView, LessonView, SearchView,
@ -86,6 +86,7 @@ urlpatterns = [
path('contest/create', ContestEditView.as_view(), name='contest_create'),
path('contest/<int:pk>/edit', ContestEditView.as_view(), name='contest_edit'),
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>
<div class="contest-works"></div>
<div class="contest-works">
<contest-work v-for="contestWork in contestWorks" :contest-work="contestWork"></contest-work>
</div>
</template>
<script>
import {api} from "../js/modules/api";
import ContestWork from './blocks/ContestWork';
import ContestWork from "./blocks/ContestWork.vue";
export default {
name: "contest-work",
prop: ['contestId'],
name: "contest-works",
props: ['contestId'],
data(){
return {
contestWorks: [],
};
},
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>
<style>
.contest-works::after {
content ' '
flex-grow 99999999
.contest-works {
column-width: 300px;
column-gap: 20px;
text-align: left;
}
</style>

@ -1,6 +1,6 @@
<template>
<div ref="popup" class="upload-contest-work popup">
<div class="popup__wrap popup__wrap_md">
<div ref="popup" class="upload-contest-work popup" @click.prevent="hide">
<div class="popup__wrap popup__wrap_md" @click.stop>
<button class="popup__close" @click.prevent="hide">
<svg class="icon icon-close">
<use v-bind="{'xlink:href': $root.store.staticUrl + 'img/sprite.svg' + '#icon-close' }"></use>
@ -33,7 +33,7 @@
</div>
<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>
</form>
</div>
@ -131,7 +131,7 @@
let data = this.contestWork;
data.contest = this.contestId;
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: {
'Authorization': `Token ${this.$root.store.accessToken}`,
}

@ -1,38 +1,63 @@
<template>
<div class="contest-work" :style="style">
<i :style="{paddingBottom: imgHeight / imgWidth * 100 + '%'}"></i>
<img class="contest-work__img" :src="contestWork.image" />
<a :href="`/contest-work/${contestWork.id}/`" class="contest-work-item">
<img class="contest-work-item__img" :src="contestWork.image.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>
</a>
</template>
<script>
export default {
name: "contest-work",
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>
<style>
.contest-work__img {
position: absolute;
max-height 100%;
max-width 100%;
width auto;
<style lang="scss">
.contest-work-item {
break-inside: avoid;
border-radius: 8px;
overflow: hidden;
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>

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

@ -4097,3 +4097,31 @@ a
.anchor
padding-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