Merge remote-tracking branch 'origin/dev' into dev

remotes/origin/hasaccess
Vitaly Baev 8 years ago
commit 46d7b747bd
  1. 2
      api/v1/serializers/course.py
  2. 28
      apps/course/migrations/0032_auto_20180214_1336.py
  3. 27
      apps/course/models.py
  4. 2
      apps/course/templates/course/_items.html
  5. 27
      apps/course/templates/course/course.html
  6. 24
      apps/course/views.py
  7. 15
      web/src/components/blocks/BlockImages.vue
  8. 4
      web/src/components/inputs/LilSelect.vue
  9. 7
      web/src/js/modules/api.js
  10. 2
      web/src/sass/_common.sass

@ -138,7 +138,7 @@ class CourseCreateSerializer(DispatchContentMixin,
materials = validated_data.pop('materials', []) materials = validated_data.pop('materials', [])
gallery = validated_data.pop('gallery', {}) gallery = validated_data.pop('gallery', {})
author = validated_data.get('author', None) author = validated_data.get('author', None)
if not author: if not instance.author or not author:
validated_data['author'] = self.context['request'].user validated_data['author'] = self.context['request'].user
course = super().update(instance, validated_data) course = super().update(instance, validated_data)
self.dispatch_materials(course, materials) self.dispatch_materials(course, materials)

@ -0,0 +1,28 @@
# Generated by Django 2.0.2 on 2018-02-14 13:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0031_auto_20180213_0906'),
]
operations = [
migrations.AlterField(
model_name='course',
name='short_description',
field=models.TextField(db_index=True, default='', verbose_name='Краткое описание курса'),
),
migrations.AlterField(
model_name='course',
name='status',
field=models.PositiveSmallIntegerField(choices=[(0, 'Draft'), (1, 'Pending'), (2, 'Published'), (3, 'Archived'), (4, 'Denied')], default=0, verbose_name='Статус'),
),
migrations.AlterField(
model_name='course',
name='title',
field=models.CharField(db_index=True, default='', max_length=100, verbose_name='Название курса'),
),
]

@ -11,7 +11,7 @@ from project.mixins import BaseModel, DeactivatedMixin
from .manager import CategoryQuerySet from .manager import CategoryQuerySet
from apps.content.models import ImageObject, Gallery from apps.content.models import ImageObject, Gallery, Video
User = get_user_model() User = get_user_model()
@ -45,10 +45,11 @@ class Course(BaseModel, DeactivatedMixin):
max_length=100, unique=True, db_index=True, max_length=100, unique=True, db_index=True,
) )
author = models.ForeignKey( author = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True) User, on_delete=models.SET_NULL, null=True, blank=True
title = models.CharField('Название курса', max_length=100, db_index=True) )
title = models.CharField('Название курса', default='', max_length=100, db_index=True)
short_description = models.TextField( short_description = models.TextField(
'Краткое описание курса', db_index=True 'Краткое описание курса', default='', db_index=True
) )
from_author = models.TextField( from_author = models.TextField(
'От автора', default='', null=True, blank=True 'От автора', default='', null=True, blank=True
@ -84,6 +85,14 @@ class Course(BaseModel, DeactivatedMixin):
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)
class Meta:
verbose_name = 'Курс'
verbose_name_plural = 'Курсы'
ordering = ['-created_at']
def __str__(self):
return str(self.id) + ' ' + self.title
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.slug: if not self.slug:
self.slug = slugify( self.slug = slugify(
@ -126,13 +135,9 @@ class Course(BaseModel, DeactivatedMixin):
return True return True
return False return False
def __str__(self): @property
return str(self.id) + ' ' + self.title def count_videos_in_lessons(self):
return Video.objects.filter(lesson__in=self.lessons.all()).count()
class Meta:
verbose_name = 'Курс'
verbose_name_plural = 'Курсы'
ordering = ['-created_at']
class Category(models.Model): class Category(models.Model):

@ -8,7 +8,7 @@
> >
<a class="courses__preview" href="{% url 'course' course.id %}?next={{ request.get_full_path }}"> <a class="courses__preview" href="{% url 'course' course.id %}?next={{ request.get_full_path }}">
{% if course.cover %} {% if course.cover %}
<img class="courses__pic" src="{{ course.cover.url }}"/> <img class="courses__pic" src="{{ course.cover.image.url }}"/>
{% else %} {% else %}
<img class="courses__pic" src="{% static 'img/no_cover.png' %}"/> <img class="courses__pic" src="{% static 'img/no_cover.png' %}"/>
{% endif %} {% endif %}

@ -100,7 +100,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-date"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-date"></use>
</svg> </svg>
</div> </div>
<div class="meta__title">15 ноября</div> <div class="meta__title">{{ course.created_at | date:"d F Yг." }}</div>
</div> </div>
<div class="meta__item"> <div class="meta__item">
<div class="meta__icon"> <div class="meta__icon">
@ -126,7 +126,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-video"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-video"></use>
</svg> </svg>
</div> </div>
<div class="meta__title">12 видео</div> <div class="meta__title">{{ course.count_videos_in_lessons }} видео</div>
</div> </div>
</div> </div>
</div> </div>
@ -142,7 +142,11 @@
</button> </button>
</div> </div>
<a class="course__video video" href="#"> <a class="course__video video" href="#">
<img class="video__pic" src="{% static 'img/video-1.jpg' %}"> {% if course.cover %}
<img class="video__pic" src="{{ course.cover.image.url }}"/>
{% else %}
<img class="video__pic" src="{% static 'img/no_cover.png' %}"/>
{% endif %}
{% if course.is_deferred_start %} {% if course.is_deferred_start %}
<div class="video__soon"> <div class="video__soon">
<div class="video__title">Курс начнется:</div> <div class="video__title">Курс начнется:</div>
@ -201,14 +205,14 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% if user.is_authenticated %} {% if user.is_authenticated and course.lessons.exists %}
<div class="section"> <div class="section">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="lessons"> <div class="lessons">
<div class="lessons__title title">Содержание курса</div> <div class="lessons__title title">Содержание курса</div>
<div class="lessons__list"> <div class="lessons__list">
{% for lesson in course.lessons.all %} {% for lesson in course.lessons.all %}
<a href="{% url 'lesson' pk=lesson.id %}"> <a href="{% if request.user.role == request.user.AUTHOR_ROLE or request.user.role == request.user.ADMIN_ROLE %}{% url 'lesson' pk=lesson.id %}{% else %}#{% endif %}">
<div class="lessons__item"> <div class="lessons__item">
<div class="lessons__subtitle subtitle">{{ lesson.title }}</div> <div class="lessons__subtitle subtitle">{{ lesson.title }}</div>
<div class="lessons__row"> <div class="lessons__row">
@ -233,7 +237,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="section"> {% comment %} <div class="section">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Материалы, которые понадобятся</div> <div class="title">Материалы, которые понадобятся</div>
<div class="materials"> <div class="materials">
@ -256,14 +260,19 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div> {% endcomment %}
{% endif %} {% endif %}
{% if course.gallery %}
<div class="section section_gradient"> <div class="section section_gradient">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
{% include "course/content/gallery.html" with results=True %} {% include "course/content/gallery.html" with results=True %}
</div> </div>
</div> </div>
<div class="course course_promo" style="background-image: url({% static 'img/video-1.jpg' %});"> {% endif %}
<div
class="course course_promo"
style="background-image: url({% if course.cover %}{{ course.cover.image.url }}{% else %}{% static 'img/no_cover.png' %}{% endif %});"
>
<div class="course__center center center_sm"> <div class="course__center center center_sm">
<div class="course__head"> <div class="course__head">
<div class="course__theme theme theme_green">ПЕРСОНАЖИ</div> <div class="course__theme theme theme_green">ПЕРСОНАЖИ</div>
@ -326,7 +335,7 @@
<use xlink:href="{% static 'img/sprite.svg' %}#icon-date"></use> <use xlink:href="{% static 'img/sprite.svg' %}#icon-date"></use>
</svg> </svg>
</div> </div>
<div class="meta__title">15 ноября</div> <div class="meta__title">{{ course.created_at | date:"d F Yг." }}</div>
</div> </div>
<div class="meta__item"> <div class="meta__item">
<div class="meta__icon"> <div class="meta__icon">

@ -1,14 +1,18 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models import Q from django.db.models import Q
from django.http import JsonResponse from django.http import JsonResponse, Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template import loader, Context, Template from django.template import loader, Context, Template
from django.views.generic import View, CreateView, DetailView, ListView, TemplateView from django.views.generic import View, CreateView, DetailView, ListView, TemplateView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from .models import Course, Like, Lesson, CourseComment, LessonComment from .models import Course, Like, Lesson, CourseComment, LessonComment
from .filters import CourseFilter from .filters import CourseFilter
User = get_user_model()
@login_required @login_required
@csrf_exempt @csrf_exempt
@ -154,11 +158,20 @@ class CourseEditView(TemplateView):
return context return context
@method_decorator(login_required, name='dispatch')
class CourseView(DetailView): class CourseView(DetailView):
model = Course model = Course
context_object_name = 'course' context_object_name = 'course'
template_name = 'course/course.html' template_name = 'course/course.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
if (self.object.status != Course.PUBLISHED and
(request.user.role not in [User.AUTHOR_ROLE, User.ADMIN_ROLE] or
self.object.author != request.user)):
raise Http404
return response
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['next'] = self.request.GET.get('next', None) context['next'] = self.request.GET.get('next', None)
@ -228,11 +241,20 @@ class CoursesView(ListView):
return 'course/courses.html' return 'course/courses.html'
@method_decorator(login_required, name='dispatch')
class LessonView(DetailView): class LessonView(DetailView):
model = Lesson model = Lesson
context_object_name = 'lesson' context_object_name = 'lesson'
template_name = 'course/lesson.html' template_name = 'course/lesson.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
if (self.object.course.status != Course.PUBLISHED and
(request.user.role not in [User.AUTHOR_ROLE, User.ADMIN_ROLE] or
self.object.course.author != request.user)):
raise Http404
return response
class SearchView(CoursesView): class SearchView(CoursesView):
template_name = 'course/result.html' template_name = 'course/result.html'

@ -25,8 +25,13 @@
</div> </div>
</div> </div>
<div class="kit__gallery"> <div class="kit__gallery">
<div class="kit__preview" v-for="image in images" v-bind:class="{ 'kit__preview--loading': image.loading }"> <div class="kit__preview" v-for="(image, index) in images" v-bind:class="{ 'kit__preview--loading': image.loading }">
<img :src="image.src" class="kit__pic"> <img :src="image.src" class="kit__pic">
<button type="button" @click="onRemoveImage(index)">
<svg class="icon icon-delete">
<use xlink:href="/static/img/sprite.svg#icon-delete"></use>
</svg>
</button>
</div> </div>
<div class="kit__photo"> <div class="kit__photo">
<svg class="icon icon-add-plus"> <svg class="icon icon-add-plus">
@ -85,6 +90,14 @@
}, },
onRemove() { onRemove() {
this.$emit('remove', this.index); this.$emit('remove', this.index);
},
onRemoveImage(index) {
let images = this.images;
let id = images[index].img;
images.splice(index, 1);
this.$emit('update:images', images);
api.removeImage(id, this.accessToken);
} }
} }
} }

@ -4,8 +4,8 @@
{{ selectedTitle }} {{ selectedTitle }}
</div> </div>
<div class="select__drop"> <div class="select__drop">
<div class="select__option" v-for="option in options"> <div class="select__option" v-for="option in options" @click.stop.prevent="selectOption(option)">
<div class="select__title" @click.stop.prevent="selectOption(option)">{{ option.title }}</div> <div class="select__title">{{ option.title }}</div>
</div> </div>
</div> </div>
</div> </div>

@ -21,6 +21,13 @@ export const api = {
} }
}); });
}, },
removeImage: (imageId, accessToken) => {
return axios.delete(`/api/v1/image-objects/${imageId}/`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
loadCourse: (courseId, accessToken) => { loadCourse: (courseId, accessToken) => {
return axios.get(`/api/v1/courses/${courseId}/`, { return axios.get(`/api/v1/courses/${courseId}/`, {
headers: { headers: {

@ -1872,7 +1872,7 @@ a.grey-link
.info__field--light .info__field--light
.select .select
.select__head .select__head
color: #777 color: #fff
font-size: 15px font-size: 15px
&:after &:after
border-color: #fff transparent transparent transparent border-color: #fff transparent transparent transparent

Loading…
Cancel
Save