Merge feature/api

remotes/origin/hasaccess
Vitaly Baev 8 years ago
commit 386ef31850
  1. 261
      api/v1/serializers/course.py
  2. 152
      api/v1/serializers/mixins.py
  3. 4
      apps/content/admin.py
  4. 8
      apps/content/models.py
  5. 23
      apps/course/migrations/0031_auto_20180213_0906.py
  6. 6
      apps/course/models.py
  7. 19
      apps/course/templates/course/content/gallery.html
  8. 6
      apps/course/templates/course/content/image.html
  9. 9
      apps/course/templates/course/content/imagetext.html
  10. 6
      apps/course/templates/course/content/text.html
  11. 13
      apps/course/templates/course/content/video.html
  12. 50
      apps/course/templates/course/course.html
  13. 24
      apps/course/templates/course/course_edit.html
  14. 13
      apps/course/templates/course/lesson.html
  15. 43
      apps/course/views.py
  16. 2
      apps/user/fixtures/superuser.json
  17. 17
      apps/user/migrations/0008_auto_20180212_0750.py
  18. 37
      apps/user/models.py
  19. 38
      project/mixins.py
  20. 13
      project/templates/lilcity/index.html
  21. 3
      project/urls.py
  22. 24
      web/src/sass/_common.sass

@ -12,6 +12,8 @@ from apps.content.models import (
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
) )
from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin
class MaterialCreateSerializer(serializers.ModelSerializer): class MaterialCreateSerializer(serializers.ModelSerializer):
@ -69,7 +71,11 @@ class CategorySerializer(serializers.ModelSerializer):
) )
class CourseCreateSerializer(serializers.ModelSerializer): class CourseCreateSerializer(DispatchContentMixin,
DispatchGalleryMixin,
DispatchMaterialMixin,
serializers.ModelSerializer
):
slug = serializers.SlugField(allow_unicode=True, required=False) slug = serializers.SlugField(allow_unicode=True, required=False)
content = serializers.ListSerializer( content = serializers.ListSerializer(
child=ContentCreateSerializer(), child=ContentCreateSerializer(),
@ -100,6 +106,7 @@ class CourseCreateSerializer(serializers.ModelSerializer):
'materials', 'materials',
'created_at', 'created_at',
'update_at', 'update_at',
'deactivated_at',
'content', 'content',
'gallery', 'gallery',
) )
@ -109,146 +116,16 @@ class CourseCreateSerializer(serializers.ModelSerializer):
'url', 'url',
'created_at', 'created_at',
'update_at', 'update_at',
'deactivated_at',
) )
def dispatch_content(self, course, content, materials):
for c in content:
if c['type'] == 'text':
if 'id' in c['data'] and c['data']['id']:
t = Text.objects.get(id=c['data']['id'])
t.position = c['data']['position']
t.title = c['data']['title']
t.course = course
t.txt = c['data']['txt']
t.save()
else:
t = Text.objects.create(
position=c['data']['position'],
title=c['data']['title'],
course=course,
txt=c['data']['txt'],
)
elif c['type'] == 'image':
if 'id' in c['data'] and c['data']['id']:
image = Image.objects.get(id=c['data']['id'])
image.position = c['data']['position']
image.title = c['data']['title']
image.course = course
image.img = ImageObject.objects.get(id=c['data']['img'])
image.save()
else:
image = Image.objects.create(
position=c['data']['position'],
title=c['data']['title'],
course=course,
img=ImageObject.objects.get(id=c['data']['img']),
)
elif c['type'] == 'image-text':
if 'id' in c['data'] and c['data']['id']:
it = ImageText.objects.get(id=c['data']['id'])
it.position = c['data']['position']
it.title = c['data']['title']
it.course = course
it.img = ImageObject.objects.get(id=c['data']['img'])
it.txt = c['data']['txt']
it.save()
else:
it = ImageText.objects.create(
position=c['data']['position'],
title=c['data']['title'],
course=course,
img=ImageObject.objects.get(id=c['data']['img']),
txt=c['data']['txt'],
)
elif c['type'] == 'video':
if 'id' in c['data'] and c['data']['id']:
v = Video.objects.get(id=c['data']['id'])
v.position = c['data']['position']
v.title = c['data']['title']
v.course = course
v.url = c['data']['url']
v.save()
else:
v = Video.objects.create(
position=c['data']['position'],
title=c['data']['title'],
course=course,
url=c['data']['url'],
)
elif c['type'] == 'images':
if 'id' in c['data'] and c['data']['id']:
g = Gallery.objects.get(id=c['data']['id'])
g.course = course
g.position = c['data']['position']
g.title = c['data']['title']
g.save()
if 'images' in c['data']:
for image in c['data']['images']:
gi = GalleryImage.objects.create(
gallery=g,
img=ImageObject.objects.get(id=image['img'])
)
else:
g = Gallery.objects.create(
course=course,
position=c['data']['position'],
title=c['data']['title'],
)
if 'images' in c['data']:
for image in c['data']['images']:
gi = GalleryImage.objects.create(
gallery=g,
img=ImageObject.objects.get(id=image['img']),
)
for material in materials:
if 'id' in material and material['id']:
m = Material.objects.get(id=material['id'])
m.title = material['title']
m.cover = ImageObject.objects.get(id=material['cover'])
m.short_description = material['short_description']
m.save()
else:
m = Material.objects.create(
title=material['title'],
cover=ImageObject.objects.get(id=material['cover']),
short_description=material['short_description'],
)
course.materials.add(m)
def dispatch_gallery(self, course, gallery):
if gallery:
if 'id' in gallery and gallery['id']:
g = Gallery.objects.get(id=gallery['id'])
g.title = gallery.get('title', g.title)
g.position = 0
g.save()
else:
g = Gallery.objects.create(
title=gallery.get('title', ''),
position=0,
)
if 'images' in gallery:
for image in gallery['images']:
if 'id' in image and image['id']:
gi = GalleryImage.objects.get(id=image['id'])
gi.gallery = g
gi.img = image['img']
gi.save()
else:
gi = GalleryImage.objects.create(
gallery=g,
img=image['img'],
)
course.gallery = g
course.save()
def create(self, validated_data): def create(self, validated_data):
content = validated_data.pop('content', []) content = validated_data.pop('content', [])
materials = validated_data.pop('materials', []) materials = validated_data.pop('materials', [])
gallery = validated_data.pop('gallery', {}) gallery = validated_data.pop('gallery', {})
course = super().create(validated_data) course = super().create(validated_data)
self.dispatch_content(course, content, materials) self.dispatch_content(course, content)
self.dispatch_materials(course, materials)
self.dispatch_gallery(course, gallery) self.dispatch_gallery(course, gallery)
return course return course
@ -257,10 +134,14 @@ class CourseCreateSerializer(serializers.ModelSerializer):
materials = validated_data.pop('materials', []) materials = validated_data.pop('materials', [])
gallery = validated_data.pop('gallery', {}) gallery = validated_data.pop('gallery', {})
course = super().update(instance, validated_data) course = super().update(instance, validated_data)
self.dispatch_materials(course, materials)
self.dispatch_content(course, content, materials) self.dispatch_content(course, content, materials)
self.dispatch_gallery(course, gallery) self.dispatch_gallery(course, gallery)
return course return course
def to_representation(self, instance):
return CourseSerializer(instance, context=self.context).to_representation(instance)
class CourseSerializer(CourseCreateSerializer): class CourseSerializer(CourseCreateSerializer):
category = CategorySerializer() category = CategorySerializer()
@ -287,87 +168,93 @@ class LessonCreateSerializer(serializers.ModelSerializer):
'content', 'content',
'created_at', 'created_at',
'update_at', 'update_at',
'deactivated_at',
) )
read_only_fields = ( read_only_fields = (
'id', 'id',
'created_at', 'created_at',
'update_at', 'update_at',
'deactivated_at',
) )
def dispatch_content(self, lesson, validated_data, content): def dispatch_content(self, lesson, content):
for c in content: for c in content:
if c['type'] == 'text': if 'type' not in c or not c['type'] or 'data' not in c or not c['data']:
if 'id' in c['data'] and c['data']['id']: continue
t = Text.objects.get(id=c['data']['id']) ctype = c['type']
t.position = c['data']['position'] cdata = c['data']
t.title = c['data']['title'] if ctype == 'text':
if 'id' in cdata and cdata['id']:
t = Text.objects.get(id=cdata['id'])
t.position = cdata['position']
t.title = cdata['title']
t.lesson = lesson t.lesson = lesson
t.txt = c['data']['txt'] t.txt = cdata['txt']
t.save() t.save()
else: else:
t = Text.objects.create( t = Text.objects.create(
position=c['data']['position'], position=cdata['position'],
title=c['data']['title'], title=cdata['title'],
lesson=lesson, lesson=lesson,
txt=c['data']['txt'], txt=cdata['txt'],
) )
elif c['type'] == 'image': elif ctype == 'image':
if 'id' in c['data'] and c['data']['id']: if 'id' in cdata and cdata['id']:
image = Image.objects.get(id=c['data']['id']) image = Image.objects.get(id=cdata['id'])
image.position = c['data']['position'] image.position = cdata['position']
image.title = c['data']['title'] image.title = cdata['title']
image.lesson = lesson image.lesson = lesson
image.img = ImageObject.objects.get(id=c['data']['img']) image.img = ImageObject.objects.get(id=cdata['img'])
image.save() image.save()
else: else:
image = Image.objects.create( image = Image.objects.create(
position=c['data']['position'], position=cdata['position'],
title=c['data']['title'], title=cdata['title'],
lesson=lesson, lesson=lesson,
img=ImageObject.objects.get(id=c['data']['img']), img=ImageObject.objects.get(id=cdata['img']),
) )
elif c['type'] == 'image-text': elif ctype == 'image-text':
if 'id' in c['data'] and c['data']['id']: if 'id' in cdata and cdata['id']:
it = ImageText.objects.get(id=c['data']['id']) it = ImageText.objects.get(id=cdata['id'])
it.position = c['data']['position'] it.position = cdata['position']
it.title = c['data']['title'] it.title = cdata['title']
it.lesson = lesson it.lesson = lesson
it.img = ImageObject.objects.get(id=c['data']['img']) it.img = ImageObject.objects.get(id=cdata['img'])
it.txt = c['data']['txt'] it.txt = cdata['txt']
it.save() it.save()
else: else:
it = ImageText.objects.create( it = ImageText.objects.create(
position=c['data']['position'], position=cdata['position'],
title=c['data']['title'], title=cdata['title'],
lesson=lesson, lesson=lesson,
img=ImageObject.objects.get(id=c['data']['img']), img=ImageObject.objects.get(id=cdata['img']),
txt=c['data']['txt'], txt=cdata['txt'],
) )
elif c['type'] == 'video': elif ctype == 'video':
if 'id' in c['data'] and c['data']['id']: if 'id' in cdata and cdata['id']:
v = Video.objects.get(id=c['data']['id']) v = Video.objects.get(id=cdata['id'])
v.position = c['data']['position'] v.position = cdata['position']
v.title = c['data']['title'] v.title = cdata['title']
v.lesson = lesson v.lesson = lesson
v.url = c['data']['url'] v.url = cdata['url']
v.save() v.save()
else: else:
v = Video.objects.create( v = Video.objects.create(
position=c['data']['position'], position=cdata['position'],
title=c['data']['title'], title=cdata['title'],
lesson=lesson, lesson=lesson,
url=c['data']['url'], url=cdata['url'],
) )
elif c['type'] == 'images': elif ctype == 'images':
if 'id' in c['data'] and c['data']['id']: if 'id' in cdata and cdata['id']:
g = Gallery.objects.get(id=c['data']['id']) g = Gallery.objects.get(id=cdata['id'])
g.position = c['data']['position'] g.position = cdata['position']
g.title = c['data']['title'] g.title = cdata['title']
g.lesson = lesson g.lesson = lesson
g.save() g.save()
if 'images' in c['data']: if 'images' in cdata:
for image in c['data']['images']: for image in cdata['images']:
gi = GalleryImage.objects.create( gi = GalleryImage.objects.create(
gallery=g, gallery=g,
img=ImageObject.objects.get(id=image['img']), img=ImageObject.objects.get(id=image['img']),
@ -375,11 +262,11 @@ class LessonCreateSerializer(serializers.ModelSerializer):
else: else:
g = Gallery.objects.create( g = Gallery.objects.create(
lesson=lesson, lesson=lesson,
position=c['data']['position'], position=cdata['position'],
title=c['data']['title'], title=cdata['title'],
) )
if 'images' in c['data']: if 'images' in cdata:
for image in c['data']['images']: for image in cdata['images']:
gi = GalleryImage.objects.create( gi = GalleryImage.objects.create(
gallery=g, gallery=g,
img=ImageObject.objects.get(id=image['img']), img=ImageObject.objects.get(id=image['img']),
@ -388,17 +275,17 @@ class LessonCreateSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
content = validated_data.pop('content', []) content = validated_data.pop('content', [])
lesson = super().create(validated_data) lesson = super().create(validated_data)
self.dispatch_content(lesson, validated_data, content) self.dispatch_content(lesson, content)
return lesson return lesson
def update(self, instance, validated_data): def update(self, instance, validated_data):
content = validated_data.pop('content', []) content = validated_data.pop('content', [])
lesson = super().update(instance, validated_data) lesson = super().update(instance, validated_data)
self.dispatch_content(lesson, validated_data, content) self.dispatch_content(lesson, content)
return lesson return lesson
class LessonSerializer(LessonCreateSerializer): class LessonSerializer(LessonCreateSerializer):
course = CourseSerializer() course = CourseSerializer()
cover = ImageObjectSerializer() cover = ImageObjectSerializer()
content = ContentSerializer() content = ContentSerializer(many=True)

@ -0,0 +1,152 @@
from apps.course.models import Category, Course, Material, Lesson, Like
from apps.content.models import (
Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject,
)
class DispatchContentMixin(object):
def dispatch_content(self, course, content):
for c in content:
if 'type' not in c or not c['type'] or 'data' not in c or not c['data']:
continue
ctype = c['type']
cdata = c['data']
if ctype == 'text':
if 'id' in cdata and cdata['id']:
t = Text.objects.get(id=cdata['id'])
t.position = cdata['position']
t.title = cdata['title']
t.course = course
t.txt = cdata['txt']
t.save()
else:
t = Text.objects.create(
position=cdata['position'],
title=cdata['title'],
course=course,
txt=cdata['txt'],
)
elif ctype == 'image':
if 'id' in cdata and cdata['id']:
image = Image.objects.get(id=cdata['id'])
image.position = cdata['position']
image.title = cdata['title']
image.course = course
image.img = ImageObject.objects.get(id=cdata['img'])
image.save()
else:
image = Image.objects.create(
position=cdata['position'],
title=cdata['title'],
course=course,
img=ImageObject.objects.get(id=cdata['img']),
)
elif ctype == 'image-text':
if 'id' in cdata and cdata['id']:
it = ImageText.objects.get(id=cdata['id'])
it.position = cdata['position']
it.title = cdata['title']
it.course = course
it.img = ImageObject.objects.get(id=cdata['img'])
it.txt = cdata['txt']
it.save()
else:
it = ImageText.objects.create(
position=cdata['position'],
title=cdata['title'],
course=course,
img=ImageObject.objects.get(id=cdata['img']),
txt=cdata['txt'],
)
elif ctype == 'video':
if 'id' in cdata and cdata['id']:
v = Video.objects.get(id=cdata['id'])
v.position = cdata['position']
v.title = cdata['title']
v.course = course
v.url = cdata['url']
v.save()
else:
v = Video.objects.create(
position=cdata['position'],
title=cdata['title'],
course=course,
url=cdata['url'],
)
elif ctype == 'images':
if 'id' in cdata and cdata['id']:
g = Gallery.objects.get(id=cdata['id'])
g.course = course
g.position = cdata['position']
g.title = cdata['title']
g.save()
if 'images' in cdata:
for image in cdata['images']:
gi = GalleryImage.objects.create(
gallery=g,
img=ImageObject.objects.get(id=image['img'])
)
else:
g = Gallery.objects.create(
course=course,
position=cdata['position'],
title=cdata['title'],
)
if 'images' in cdata:
for image in cdata['images']:
gi = GalleryImage.objects.create(
gallery=g,
img=ImageObject.objects.get(id=image['img']),
)
class DispatchMaterialMixin(object):
def dispatch_materials(self, course, materials):
for material in materials:
if 'id' in material and material['id']:
m = Material.objects.get(id=material['id'])
m.title = material['title']
m.cover = ImageObject.objects.get(id=material['cover'])
m.short_description = material['short_description']
m.save()
else:
m = Material.objects.create(
title=material['title'],
cover=ImageObject.objects.get(id=material['cover']),
short_description=material['short_description'],
)
course.materials.add(m)
class DispatchGalleryMixin(object):
def dispatch_gallery(self, course, gallery):
if gallery:
if 'id' in gallery and gallery['id']:
g = Gallery.objects.get(id=gallery['id'])
g.title = gallery.get('title', g.title)
g.position = 0
g.save()
else:
g = Gallery.objects.create(
title=gallery.get('title', ''),
position=0,
)
if 'images' in gallery:
for image in gallery['images']:
if 'id' in image and image['id']:
gi = GalleryImage.objects.get(id=image['id'])
gi.gallery = g
gi.img = image['img']
gi.save()
else:
gi = GalleryImage.objects.create(
gallery=g,
img=image['img'],
)
course.gallery = g
course.save()

@ -23,6 +23,7 @@ class ImageObjectAdmin(admin.ModelAdmin):
class ContentChildAdmin(PolymorphicChildModelAdmin): class ContentChildAdmin(PolymorphicChildModelAdmin):
base_model = Content base_model = Content
show_in_index = True
base_fieldsets = ( base_fieldsets = (
(None, {'fields': ('course', 'lesson', 'title', 'position',)}), (None, {'fields': ('course', 'lesson', 'title', 'position',)}),
) )
@ -56,12 +57,13 @@ class GalleryAdmin(ContentChildAdmin):
@admin.register(Content) @admin.register(Content)
class ContentAdmin(PolymorphicParentModelAdmin): class ContentAdmin(PolymorphicParentModelAdmin):
base_model = Content base_model = Content
polymorphic_list = True
child_models = ( child_models = (
Image, Image,
Text, Text,
ImageText, ImageText,
Video, Video,
GalleryAdmin, # GalleryAdmin,
) )

@ -40,7 +40,10 @@ class Content(PolymorphicModel):
class Meta: class Meta:
verbose_name = 'Контент' verbose_name = 'Контент'
verbose_name_plural = 'Контент' verbose_name_plural = 'Контент'
ordering = ('-created_at',) ordering = ('position', '-created_at',)
def ctype(self):
return self.__class__.__name__.lower()
class Image(Content): class Image(Content):
@ -65,6 +68,9 @@ class ImageText(Content):
class Video(Content): class Video(Content):
url = models.URLField('Ссылка') url = models.URLField('Ссылка')
def video_index(self):
return self.url.split('/')[-1]
class Gallery(Content): class Gallery(Content):
pass pass

@ -0,0 +1,23 @@
# Generated by Django 2.0.2 on 2018-02-13 09:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0030_auto_20180212_0537'),
]
operations = [
migrations.AddField(
model_name='course',
name='deactivated_at',
field=models.DateTimeField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='lesson',
name='deactivated_at',
field=models.DateTimeField(blank=True, default=None, null=True),
),
]

@ -7,6 +7,8 @@ from django.contrib.auth import get_user_model
from django.urls import reverse_lazy from django.urls import reverse_lazy
from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey
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
@ -25,7 +27,7 @@ def default_slug():
return str(uuid4()) return str(uuid4())
class Course(models.Model): class Course(BaseModel, DeactivatedMixin):
PENDING = 0 PENDING = 0
PUBLISHED = 1 PUBLISHED = 1
ARCHIVED = 2 ARCHIVED = 2
@ -141,7 +143,7 @@ class Category(models.Model):
ordering = ['title'] ordering = ['title']
class Lesson(models.Model): class Lesson(BaseModel, DeactivatedMixin):
title = models.CharField('Название урока', max_length=100) title = models.CharField('Название урока', max_length=100)
short_description = models.TextField('Краткое описание урока') short_description = models.TextField('Краткое описание урока')
course = models.ForeignKey( course = models.ForeignKey(

@ -0,0 +1,19 @@
{% if results %}
<div class="title">Галерея итогов обучения</div>
<div class="examples">
{% for image in course.gallery.gallery_images.all %}
<div class="examples__item">
<img class="examples__pic" src="{{ image.img.image.url }}">
</div>
{% endfor %}
</div>
{% else %}
<div class="content-block title">{{ content.title }}</div>
<div class="examples">
{% for image in content.gallery_images.all %}
<div class="examples__item">
<img class="examples__pic" src="{{ image.img.image.url }}">
</div>
{% endfor %}
</div>
{% endif %}

@ -0,0 +1,6 @@
<div class="content-block title">
{{ content.title }}
</div>
<div>
<img class="content-block pic" src="{{ content.img.image.url }}" alt="">
</div>

@ -0,0 +1,9 @@
<div class="content-block title">
{{ content.title }}
</div>
<div class="content-block text">
{{ content.txt }}
</div>
<div>
<img class="content-block pic" src="{{ content.img.image.url }}" alt="">
</div>

@ -0,0 +1,6 @@
<div class="content-block title">
{{ content.title }}
</div>
<div class="content-block text">
{{ content.txt }}
</div>

@ -0,0 +1,13 @@
<div class="content-block title">
{{ content.title }}
</div>
<div>
{% if 'youtube.com' in content.url or 'youtu.be' in content.url %}
<iframe width="640" height="360" src="https://www.youtube.com/embed/{{ content.video_index }}" frameborder="0" allow="autoplay; encrypted-media"
allowfullscreen></iframe>
{% elif 'vimeo.com' in content.url %}
<iframe src="https://player.vimeo.com/video/{{ content.video_index }}" width="640" height="360" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen>
</iframe>
{% endif %}
</div>

@ -3,6 +3,7 @@
{% load data_liked from data_liked %} {% load data_liked from data_liked %}
{% load rupluralize from plural %} {% load rupluralize from plural %}
{% block title %}{{ course.title }} - {{ block.super }}{% endblock title %} {% block title %}{{ course.title }} - {{ block.super }}{% endblock title %}
{% block description %}{{ course.title }} - {{ block.super }}{% endblock description%}
{% block content %} {% block content %}
<div class="section section_border"> <div class="section section_border">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
@ -195,6 +196,15 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% for content in course.content.all %}
<div class="section section_border">
<div class="section__center center center_sm">
{% with template="course/content/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}
</div>
</div>
{% endfor %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div class="section"> <div class="section">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
@ -254,45 +264,7 @@
{% endif %} {% endif %}
<div class="section section_gradient"> <div class="section section_gradient">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Галерея итогов обучения</div> {% include "course/content/gallery.html" with results=True %}
<div class="examples">
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
<div class="examples__item">
<img class="examples__pic" src="{% static 'img/box.jpg' %}">
</div>
</div>
</div> </div>
</div> </div>
<div class="course course_promo" style="background-image: url({% static 'img/video-1.jpg' %});"> <div class="course course_promo" style="background-image: url({% static 'img/video-1.jpg' %});">

@ -0,0 +1,24 @@
{% extends "templates/lilcity/index.html" %}
{% load static %}
{% block content %}
<script>
{% if request.user.is_authenticated %}
{% autoescape off %}
window.USER = {{ request.user.serialized }}
{% endautoescape %}
{% if request.user.auth_token %}
AUTH_TOKEN = "{{ request.user.auth_token }}"
{% else %}
AUTH_TOKEN = null
{% endif %}
{% else %}
windows.USER = null
{% endif %}
</script>
<div class="section section_border">
<div class="section__center center center_sm">
{{course}}
{{course.id}}
</div>
</div>
{% endblock content %}

@ -35,7 +35,16 @@
</div> </div>
</div> </div>
</div> </div>
<div class="section section_gradient"> {% for content in lesson.content.all %}
<div class="section section_border">
<div class="section__center center center_sm">
{% with template="course/content/"|add:content.ctype|add:".html" %}
{% include template %}
{% endwith %}
</div>
</div>
{% endfor %}
{% comment %} <div class="section section_gradient">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Примеры техники</div> <div class="title">Примеры техники</div>
<div class="examples"> <div class="examples">
@ -77,7 +86,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div> {% endcomment %}
<div class="section section_gray"> <div class="section section_gray">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
<div class="title">Задавайте вопросы:</div> <div class="title">Задавайте вопросы:</div>

@ -1,8 +1,9 @@
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
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 from django.views.generic import View, CreateView, DetailView, ListView, TemplateView
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
@ -136,6 +137,19 @@ def lessoncomment(request, lesson_id):
}) })
class CourseEditView(TemplateView):
template_name = 'course/course_edit.html'
def get(self, request, pk=None):
self.object = get_object_or_404(Course, pk=pk)
return super().get(request)
def get_context_data(self):
context = super().get_context_data()
context['course'] = self.object
return context
class CourseView(DetailView): class CourseView(DetailView):
model = Course model = Course
context_object_name = 'course' context_object_name = 'course'
@ -146,6 +160,14 @@ class CourseView(DetailView):
context['next'] = self.request.GET.get('next', None) context['next'] = self.request.GET.get('next', None)
return context return context
def get_queryset(self):
queryset = super().get_queryset().select_related(
'author', 'category',
).prefetch_related(
'likes', 'materials', 'content',
)
return queryset
class CoursesView(ListView): class CoursesView(ListView):
model = Course model = Course
@ -158,12 +180,17 @@ class CoursesView(ListView):
if request.is_ajax(): if request.is_ajax():
context = self.get_context_data() context = self.get_context_data()
template_name = self.get_template_names() template_name = self.get_template_names()
html = loader.render_to_string(template_name, context, request=request) html = loader.render_to_string(
template_name, context, request=request)
is_paginated = context.get('is_paginated') is_paginated = context.get('is_paginated')
if is_paginated: if is_paginated:
page_obj = context.get('page_obj') page_obj = context.get('page_obj')
prev_url = request.path + '?page=' + str(page_obj.previous_page_number()) if page_obj.has_previous() else None prev_url = request.path + '?page=' + \
next_url = request.path + '?page=' + str(page_obj.next_page_number()) if page_obj.has_next() else None str(page_obj.previous_page_number()
) if page_obj.has_previous() else None
next_url = request.path + '?page=' + \
str(page_obj.next_page_number()
) if page_obj.has_next() else None
else: else:
prev_url = None prev_url = None
next_url = None next_url = None
@ -178,7 +205,7 @@ class CoursesView(ListView):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset().select_related( queryset = super().get_queryset().select_related(
'author', 'category' 'author', 'category', 'cover',
).prefetch_related( ).prefetch_related(
'likes', 'materials', 'content', 'likes', 'materials', 'content',
) )
@ -210,7 +237,8 @@ class SearchView(CoursesView):
search_query = self.request.GET.get('q', None) search_query = self.request.GET.get('q', None)
queryset = super().get_queryset() queryset = super().get_queryset()
if search_query: if search_query:
query = Q(title__icontains=search_query) | Q(short_description__icontains=search_query) query = Q(title__icontains=search_query) | Q(
short_description__icontains=search_query)
queryset = queryset.filter(query) queryset = queryset.filter(query)
else: else:
queryset = queryset.none() queryset = queryset.none()
@ -223,7 +251,8 @@ class SearchView(CoursesView):
if 'is_paginated' in context and context['is_paginated']: if 'is_paginated' in context and context['is_paginated']:
page_obj = context.get('page_obj') page_obj = context.get('page_obj')
context['page'] = page_obj.number context['page'] = page_obj.number
context['next_page'] = str(page_obj.next_page_number()) if page_obj.has_next() else None context['next_page'] = str(
page_obj.next_page_number()) if page_obj.has_next() else None
else: else:
context['page'] = 1 context['page'] = 1
context['next_page'] = None context['next_page'] = None

@ -26,7 +26,7 @@
"vkontakte": null, "vkontakte": null,
"fb_id": null, "fb_id": null,
"fb_data": {}, "fb_data": {},
"is_email_proved": false, "is_email_proved": true,
"photo": "", "photo": "",
"groups": [], "groups": [],
"user_permissions": [] "user_permissions": []

@ -0,0 +1,17 @@
# Generated by Django 2.0.2 on 2018-02-12 07:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('user', '0007_auto_20180207_0808'),
]
operations = [
migrations.AlterModelOptions(
name='user',
options={'ordering': ('-date_joined',), 'verbose_name': 'user', 'verbose_name_plural': 'users'},
),
]

@ -1,8 +1,15 @@
from django.db import models from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser, UserManager from django.contrib.auth.models import AbstractUser, UserManager
from django.contrib.postgres import fields as pgfields from django.contrib.postgres import fields as pgfields
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.models import Token
from json import dumps
from api.v1 import serializers
class User(AbstractUser): class User(AbstractUser):
USER_ROLE = 0 USER_ROLE = 0
@ -22,8 +29,10 @@ class User(AbstractUser):
(FEMALE, 'Женщина'), (FEMALE, 'Женщина'),
) )
email = models.EmailField(_('email address'), unique=True) email = models.EmailField(_('email address'), unique=True)
role = models.PositiveSmallIntegerField('Роль', default=0, choices=ROLE_CHOICES) role = models.PositiveSmallIntegerField(
gender = models.CharField('Пол', max_length=1, default='n', choices=GENDER_CHOICES) 'Роль', default=0, choices=ROLE_CHOICES)
gender = models.CharField(
'Пол', max_length=1, default='n', choices=GENDER_CHOICES)
birthday = models.DateField('День рождения', null=True, blank=True) birthday = models.DateField('День рождения', null=True, blank=True)
country = models.CharField('Страна', max_length=50, null=True, blank=True) country = models.CharField('Страна', max_length=50, null=True, blank=True)
city = models.CharField('Город', max_length=85, null=True, blank=True) city = models.CharField('Город', max_length=85, null=True, blank=True)
@ -36,7 +45,9 @@ class User(AbstractUser):
vkontakte = models.URLField('ВКонтакте', default='', null=True, blank=True) vkontakte = models.URLField('ВКонтакте', default='', null=True, blank=True)
fb_id = models.BigIntegerField(null=True, blank=True, unique=True) fb_id = models.BigIntegerField(null=True, blank=True, unique=True)
fb_data = pgfields.JSONField(default={}, null=True, blank=True) fb_data = pgfields.JSONField(default={}, null=True, blank=True)
is_email_proved = models.BooleanField('Верифицирован по email', default=False) is_email_proved = models.BooleanField(
'Верифицирован по email', default=False
)
photo = models.ImageField('Фото', null=True, blank=True, upload_to='users') photo = models.ImageField('Фото', null=True, blank=True, upload_to='users')
USERNAME_FIELD = 'email' USERNAME_FIELD = 'email'
@ -44,3 +55,23 @@ class User(AbstractUser):
class Meta(AbstractUser.Meta): class Meta(AbstractUser.Meta):
ordering = ('-date_joined',) ordering = ('-date_joined',)
def serialized(self):
user_data = serializers.user.UserSerializer(instance=self).data
user_data = dumps(user_data, ensure_ascii=False)
return user_data
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if (
(instance.is_active or instance.fb_id) and
instance.role in [User.AUTHOR_ROLE, User.ADMIN_ROLE] and not
hasattr(instance, 'auth_token')
):
Token.objects.create(user=instance)
elif (
not (instance.is_active or instance.fb_id) or
instance.role not in [User.AUTHOR_ROLE, User.ADMIN_ROLE]
) and hasattr(instance, 'auth_token'):
instance.auth_token.delete()

@ -0,0 +1,38 @@
from django.db import models
from django.utils import timezone
class BaseModel(models.Model):
class Meta:
abstract = True
class DeactivatedQueryset(models.Manager):
def allow_delete(self):
super().delete()
def delete(self):
count = self.count()
self.filter(deactivated_at__isnull=True).update(
deactivated_at=timezone.now()
)
return (count, None)
class DeactivatedMixin(models.Model):
deactivated_at = models.DateTimeField(null=True, blank=True, default=None)
objects = DeactivatedQueryset()
class Meta:
abstract = True
def allow_delete(self, using=None, keep_parents=False):
super().delete(using=using, keep_parents=keep_parents)
def delete(self, using=None, keep_parents=False):
if not self.deactivated_at:
self.deactivated_at = timezone.now()
self.save()

@ -15,7 +15,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="theme-color" content="#fff"> <meta name="theme-color" content="#fff">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<meta name="description" content="Page description"> <meta name="description" content="{% block description %}Онлайн-курсы LilCity{% endblock description%}">
<!--Twitter Card data--> <!--Twitter Card data-->
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@publisher_handle"> <meta name="twitter:site" content="@publisher_handle">
@ -135,7 +135,14 @@
{% endif %} {% endif %}
<div class="header__drop"> <div class="header__drop">
{% comment %} <a class="header__link header__link_border" href="#">234.120.345 руб.</a> {% endcomment %} {% comment %} <a class="header__link header__link_border" href="#">234.120.345 руб.</a> {% endcomment %}
{% with roles='author admin' %}
{% if request.user.role in roles.split and request.user.auth_token %}
{{request.user.auth_token}}
<a class="header__link header__link_green" href="#"> <a class="header__link header__link_green" href="#">
{% else %}
<a class="header__link header__link_gray disabled" href="#">
{% endif %}
{% endwith %}
<div class="header__title">ДОБАВИТЬ КУРС</div> <div class="header__title">ДОБАВИТЬ КУРС</div>
</a> </a>
<a class="header__link" href="{% url 'user' request.user.id %}"> <a class="header__link" href="{% url 'user' request.user.id %}">
@ -158,9 +165,11 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% if request.user.is_authenticated and not request.user.fb_id or request.user.is_authenticated and not request.user.is_email_proved %} {% if request.user.is_authenticated %}
{% if not request.user.is_email_proved and not request.user.fb_id %}
<div class="message message_error">Необходимо подтвердить электронную почту</div> <div class="message message_error">Необходимо подтвердить электронную почту</div>
{% endif %} {% endif %}
{% endif %}
</header> </header>
<div id="lilcity-vue-app" class="container"> <div id="lilcity-vue-app" class="container">
{% block content %}{% endblock content %} {% block content %}{% endblock content %}

@ -21,7 +21,7 @@ from django.conf import settings
from apps.course.views import ( from apps.course.views import (
CoursesView, likes, coursecomment, CoursesView, likes, coursecomment,
CourseView, LessonView, SearchView, CourseView, LessonView, SearchView,
lessoncomment, lessoncomment, CourseEditView,
) )
from apps.course.models import Course from apps.course.models import Course
from apps.user.views import UserView, UserEditView from apps.user.views import UserView, UserEditView
@ -33,6 +33,7 @@ urlpatterns = [
path('course/<int:pk>/', CourseView.as_view(), name='course'), path('course/<int:pk>/', CourseView.as_view(), name='course'),
path('course/<str:slug>/', CourseView.as_view(), name='course'), path('course/<str:slug>/', CourseView.as_view(), name='course'),
path('course/<int:course_id>/like', likes, name='likes'), path('course/<int:course_id>/like', likes, name='likes'),
path('course/<int:pk>/edit', CourseEditView.as_view(), name='course-edit'),
path('course/<int:course_id>/comment', coursecomment, name='coursecomment'), path('course/<int:course_id>/comment', coursecomment, name='coursecomment'),
path('lesson/<int:pk>/', LessonView.as_view(), name='lesson'), path('lesson/<int:pk>/', LessonView.as_view(), name='lesson'),
path('lesson/<int:lesson_id>/comment', lessoncomment, name='lessoncomment'), path('lesson/<int:lesson_id>/comment', lessoncomment, name='lessoncomment'),

@ -473,6 +473,10 @@ a.btn
color: $green-light color: $green-light
&:hover &:hover
color: darken($green-light,10) color: darken($green-light,10)
&_gray
color: $gray
+m
color: $gray
&_border &_border
margin-bottom: 5px margin-bottom: 5px
padding: 2px 20px 10px padding: 2px 20px 10px
@ -3268,9 +3272,19 @@ a.grey-link
a a
&.link--black &.link--black
color: #000 color: #000
&.disabled
pointer-events: none
cursor: default
opacity: 0.6
.content-block
&.title
font-size: 24px
font-style: bold
&.text
font-size: 16px
padding-bottom: 24px
text-align: left
&.pic
position: relative
width: 100%
Loading…
Cancel
Save