remotes/origin/hotfix/LIL-661
gzbender 8 years ago
parent 6630ec34ee
commit 604e2c16f6
  1. 13
      api/v1/serializers/contest.py
  2. 30
      api/v1/serializers/course.py
  3. 10
      api/v1/views.py
  4. 9
      apps/content/models.py
  5. 2
      apps/content/templates/content/contest.html
  6. 26
      apps/content/templates/content/contest_work.html
  7. 19
      apps/content/views.py
  8. 10
      web/src/components/UploadContestWork.vue
  9. 37
      web/src/components/blocks/ContestWork.vue
  10. 69
      web/src/components/blocks/Likes.vue
  11. 2
      web/src/js/app.js
  12. 12
      web/src/sass/_common.sass

@ -43,11 +43,20 @@ class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer)
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', 'img_width', 'img_height']
'created_at', 'likes', 'user_liked', 'img_width', 'img_height']
def get_likes(self, instance):
return instance.likes.count()
def get_user_liked(self, instance):
user = self.context['request'].user
return instance.likes.filter(user=user).exists() if user.is_authenticated else False
class ContestWorkCreateSerializer(serializers.ModelSerializer):
@ -55,5 +64,7 @@ class ContestWorkCreateSerializer(serializers.ModelSerializer):
model = ContestWork
fields = '__all__'
# TODO check ContestWork.objects.filter(user=self.request.user).exists()
def to_representation(self, instance):
return ContestWorkSerializer(instance=instance, context=self.context).to_representation(instance)

@ -15,7 +15,7 @@ from .content import (
from apps.content.models import (
Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
)
ContestWork)
from .user import UserSerializer
from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin
@ -63,6 +63,34 @@ class LikeSerializer(serializers.ModelSerializer):
)
class LikeCreateSerializer(serializers.ModelSerializer):
OBJ_TYPE_CONTEST_WORK = 'contest_work'
obj_type = serializers.CharField(required=True)
obj_id = serializers.IntegerField(required=True)
class Meta:
model = Like
fields = ['obj_type', 'obj_id']
def create(self, validated_data):
user = self.context['request'].user
if not (user and user.is_authenticated):
return Like()
obj_type = validated_data.pop('obj_type')
obj_id = validated_data.pop('obj_id')
if obj_type == self.OBJ_TYPE_CONTEST_WORK:
contest_work = ContestWork.objects.get(pk=obj_id)
if contest_work.likes.filter(user=user).exists():
return Like()
like = Like.objects.create(user=user)
contest_work.likes.add(like)
return like
def to_representation(self, instance):
return LikeSerializer(instance, context=self.context).to_representation(instance)
class CategorySerializer(serializers.ModelSerializer):
class Meta:

@ -14,7 +14,7 @@ from .serializers.course import (
CommentSerializer,
MaterialSerializer, MaterialCreateSerializer,
LessonSerializer, LessonCreateSerializer,
)
LikeCreateSerializer)
from .serializers.content import (
BanerSerializer,
ImageSerializer, ImageCreateSerializer,
@ -130,7 +130,11 @@ class MaterialViewSet(ExtendedModelViewSet):
class LikeViewSet(ExtendedModelViewSet):
queryset = Like.objects.select_related('user').all()
serializer_class = LikeSerializer
serializer_class = LikeCreateSerializer
serializer_class_map = {
'list': LikeSerializer,
'retrieve': LikeSerializer,
}
search_fields = ('user__email', 'user__firstname', 'user__lastname',)
ordering_fields = ('created_at', 'update_at',)
# permission_classes = (IsAdmin,)
@ -448,7 +452,7 @@ class ContestViewSet(ExtendedModelViewSet):
class ContestWorkViewSet(ExtendedModelViewSet):
queryset = ContestWork.objects.order_by('-created_at')
queryset = ContestWork.objects.all()
serializer_class = ContestWorkCreateSerializer
serializer_class_map = {
'list': ContestWorkSerializer,

@ -1,6 +1,7 @@
from urllib.parse import urlparse
from django.db import models
from django.contrib.auth import get_user_model
from django.urls import reverse_lazy
from polymorphic.models import PolymorphicModel
@ -180,6 +181,11 @@ class ContestWork(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
likes = models.ManyToManyField('course.Like', blank=True)
class Meta:
verbose_name = 'Конкурсная работа'
verbose_name_plural = 'Конкурсные работы'
ordering = ('-created_at',)
@property
def img_width(self):
return self.image.image.width if self.image and self.image.image else None
@ -187,3 +193,6 @@ class ContestWork(models.Model):
@property
def img_height(self):
return self.image.image.height if self.image and self.image.image else None
def get_absolute_url(self):
return reverse_lazy('contest_work', args=[self.id])

@ -12,7 +12,9 @@
{{ contest.description }}
</div>
<div class="main__actions">
{% if not contest_work_uploaded %}
<a class="main__btn btn" href="#" data-show-upload-contest-work>Загрузить свою работу</a>
{% endif %}
</div>
</div>
</div>

@ -2,7 +2,7 @@
{% 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 ogimage %}http://{{request.META.HTTP_HOST}}{{ contest_work.image.image.url }}{% endblock ogimage %}
{% block content %}
<div class="section" style="padding-bottom: 25px;">
@ -23,14 +23,13 @@
<img class="contest-work__img" src="{{ contest_work.image.image.url }}">
</div>
<div class="contest-work__info">
<div>
<div class="contest-work__bio">
<div>{{ contest_work.child_full_name }}</div>
<div class="contest-work__age">{{ contest_work.age }} лет</div>
<div class="contest-work__age">{{ contest_work.age }} {% if contest_work.age < 5 %}года{% else %}лет{% endif %}</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>
<likes obj-type="contest_work" obj-id="{{ contest_work.id }}"
{% if user_liked %}:user-liked="true"{% endif %} likes="{{ contest_work.likes.count }}"></likes>
</div>
</div>
</div>
@ -39,7 +38,8 @@
<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 %}">
{% 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>
@ -47,7 +47,11 @@
</div>
<div class="go__title">Предыдущая работа</div>
</a>
<a class="go__item" href="{% url 'contest' contest_work.contest.id %}">
{% 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">
@ -55,10 +59,16 @@
</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' %}
</div>
</div>
{% endblock content %}

@ -29,8 +29,27 @@ class ContestView(DetailView):
context_object_name = 'contest'
template_name = 'content/contest.html'
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
return context

@ -10,6 +10,7 @@
<form class="form">
<div class="title">
Чтобы принять участие<br>в конкурсе, заполните поля<br>и прикрепите изображение
<img src="/static/img/curve-1.svg" class="text__curve">
</div>
<div class="field"
v-bind:class="{ error: $v.contestWork.child_full_name.$dirty && $v.contestWork.child_full_name.$invalid }">
@ -138,8 +139,9 @@
});
request.then((response) => {
if(response.data.id){
this.$emit('add:contestWork', response.data);
this.$emit('add:contest-work', response.data);
this.hide();
window.location.reload();
}
});
}
@ -159,6 +161,12 @@
.title {
text-align: center; font-size: 24px;
.text__curve {
right: 55px;
width: 170px;
bottom: -40px;
}
}
.kit__photo {

@ -1,24 +1,28 @@
<template>
<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">
<a :href="`/contest-work/${contestWork.id}/`">
<img class="contest-work-item__img" :src="contestWork.image.image" />
</a>
<div class="contest-work-item__info">
<div>
<div class="contest-work-item__bio">
<div>{{ contestWork.child_full_name }}</div>
<div class="contest-work-item__age">{{ contestWork.age }} лет</div>
<div class="contest-work-item__age">{{ contestWork.age }} {{ contestWork.age < 5 ? 'года' : 'лет' }}</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>
<likes obj-type="contest_work" :obj-id="contestWork.id" :user-liked="contestWork.user_liked"
:likes="contestWork.likes"></likes>
</div>
</div>
</a>
</div>
</template>
<script>
import Likes from './Likes.vue';
export default {
name: "contest-work",
props: ['contestWork'],
components: {Likes},
}
</script>
@ -39,9 +43,6 @@
width: 100%;
height: auto;
}
.contest-works:hover .contest-work-item:not(:hover) {
opacity: 0.4;
}
.contest-work-item__info {
display: flex;
padding: 5px 10px;
@ -49,15 +50,13 @@
.contest-work-item__age {
color: #919191;
}
.contest-work-item__likes {
flex: 70px;
text-align: right;
.contest-work-item__bio {
flex: calc(100% - 70px);
}
.icon {
width: 15px;
height: 15px;
margin-left: 5px;
margin-bottom: -3px;
@media only screen and (min-width: 1023px) {
.contest-works:hover .contest-work-item:not(:hover) {
opacity: 0.4;
}
}
</style>

@ -0,0 +1,69 @@
<template>
<div class="likes" :class="{ 'likes_liked': userLikedProp }">
<span>{{ likesProp }}</span><span class="likes__like" @click.prevent="addLike">
<svg class="likes__icon icon icon-like">
<use v-bind="{'xlink:href': $root.store.staticUrl + 'img/sprite.svg' + '#icon-like' }"></use>
</svg></span>
</div>
</template>
<script>
import {api} from "../../js/modules/api";
export default {
name: 'likes',
props: ['likes', 'userLiked', 'objType', 'objId'],
data() {
return {
likesProp: +this.likes || 0,
userLikedProp: this.userLiked || false,
}
},
methods: {
addLike() {
if(this._userLiked){
return;
}
api.post('/api/v1/likes/', {
obj_type: this.objType,
obj_id: this.objId,
}, {
headers: {
'Authorization': `Token ${this.$root.store.accessToken}`,
}
})
.then((response) => {
if(response.data && response.data.id) {
this.userLikedProp = true;
this.likesProp += 1;
this.$emit('liked');
}
})
}
}
}
</script>
<style>
.likes {
font-weight: bold;
text-align: right;
}
.likes__like {
cursor: pointer;
margin-left: 5px;
}
.likes_liked .likes__like {
cursor: default;
}
.likes__icon {
margin-bottom: -3px;
}
.likes_liked .likes__icon {
fill: #d40700;
}
</style>

@ -22,6 +22,7 @@ import Vue from 'vue';
import Vuelidate from 'vuelidate';
import UploadContestWork from '../components/UploadContestWork.vue';
import ContestWorks from '../components/ContestWorks.vue';
import Likes from '../components/blocks/Likes.vue';
Vue.use(Vuelidate);
@ -40,5 +41,6 @@ const app = new Vue({
components: {
UploadContestWork,
ContestWorks,
Likes,
}
});

@ -3886,7 +3886,7 @@ a.grey-link
position: relative
padding-left: 17px
&:before
content: ''
content: ''
position: absolute
top: 0
left: 0
@ -4116,12 +4116,6 @@ a
&__age
color: #919191;
&__likes
flex: 70px;
text-align: right;
&__bio
flex: calc(100% - 70px);
&__likes .icon
width: 15px;
height: 15px;
margin-left: 5px;
margin-bottom: -3px;

Loading…
Cancel
Save