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,
)
from .mixins import DispatchContentMixin, DispatchGalleryMixin, DispatchMaterialMixin
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)
content = serializers.ListSerializer(
child=ContentCreateSerializer(),
@ -100,6 +106,7 @@ class CourseCreateSerializer(serializers.ModelSerializer):
'materials',
'created_at',
'update_at',
'deactivated_at',
'content',
'gallery',
)
@ -109,146 +116,16 @@ class CourseCreateSerializer(serializers.ModelSerializer):
'url',
'created_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):
content = validated_data.pop('content', [])
materials = validated_data.pop('materials', [])
gallery = validated_data.pop('gallery', {})
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)
return course
@ -257,10 +134,14 @@ class CourseCreateSerializer(serializers.ModelSerializer):
materials = validated_data.pop('materials', [])
gallery = validated_data.pop('gallery', {})
course = super().update(instance, validated_data)
self.dispatch_materials(course, materials)
self.dispatch_content(course, content, materials)
self.dispatch_gallery(course, gallery)
return course
def to_representation(self, instance):
return CourseSerializer(instance, context=self.context).to_representation(instance)
class CourseSerializer(CourseCreateSerializer):
category = CategorySerializer()
@ -287,87 +168,93 @@ class LessonCreateSerializer(serializers.ModelSerializer):
'content',
'created_at',
'update_at',
'deactivated_at',
)
read_only_fields = (
'id',
'created_at',
'update_at',
'deactivated_at',
)
def dispatch_content(self, lesson, validated_data, content):
def dispatch_content(self, lesson, content):
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']
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.lesson = lesson
t.txt = c['data']['txt']
t.txt = cdata['txt']
t.save()
else:
t = Text.objects.create(
position=c['data']['position'],
title=c['data']['title'],
position=cdata['position'],
title=cdata['title'],
lesson=lesson,
txt=c['data']['txt'],
txt=cdata['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']
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.lesson = lesson
image.img = ImageObject.objects.get(id=c['data']['img'])
image.img = ImageObject.objects.get(id=cdata['img'])
image.save()
else:
image = Image.objects.create(
position=c['data']['position'],
title=c['data']['title'],
position=cdata['position'],
title=cdata['title'],
lesson=lesson,
img=ImageObject.objects.get(id=c['data']['img']),
img=ImageObject.objects.get(id=cdata['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']
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.lesson = lesson
it.img = ImageObject.objects.get(id=c['data']['img'])
it.txt = c['data']['txt']
it.img = ImageObject.objects.get(id=cdata['img'])
it.txt = cdata['txt']
it.save()
else:
it = ImageText.objects.create(
position=c['data']['position'],
title=c['data']['title'],
position=cdata['position'],
title=cdata['title'],
lesson=lesson,
img=ImageObject.objects.get(id=c['data']['img']),
txt=c['data']['txt'],
img=ImageObject.objects.get(id=cdata['img']),
txt=cdata['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']
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.lesson = lesson
v.url = c['data']['url']
v.url = cdata['url']
v.save()
else:
v = Video.objects.create(
position=c['data']['position'],
title=c['data']['title'],
position=cdata['position'],
title=cdata['title'],
lesson=lesson,
url=c['data']['url'],
url=cdata['url'],
)
elif c['type'] == 'images':
if 'id' in c['data'] and c['data']['id']:
g = Gallery.objects.get(id=c['data']['id'])
g.position = c['data']['position']
g.title = c['data']['title']
elif ctype == 'images':
if 'id' in cdata and cdata['id']:
g = Gallery.objects.get(id=cdata['id'])
g.position = cdata['position']
g.title = cdata['title']
g.lesson = lesson
g.save()
if 'images' in c['data']:
for image in c['data']['images']:
if 'images' in cdata:
for image in cdata['images']:
gi = GalleryImage.objects.create(
gallery=g,
img=ImageObject.objects.get(id=image['img']),
@ -375,11 +262,11 @@ class LessonCreateSerializer(serializers.ModelSerializer):
else:
g = Gallery.objects.create(
lesson=lesson,
position=c['data']['position'],
title=c['data']['title'],
position=cdata['position'],
title=cdata['title'],
)
if 'images' in c['data']:
for image in c['data']['images']:
if 'images' in cdata:
for image in cdata['images']:
gi = GalleryImage.objects.create(
gallery=g,
img=ImageObject.objects.get(id=image['img']),
@ -388,17 +275,17 @@ class LessonCreateSerializer(serializers.ModelSerializer):
def create(self, validated_data):
content = validated_data.pop('content', [])
lesson = super().create(validated_data)
self.dispatch_content(lesson, validated_data, content)
self.dispatch_content(lesson, content)
return lesson
def update(self, instance, validated_data):
content = validated_data.pop('content', [])
lesson = super().update(instance, validated_data)
self.dispatch_content(lesson, validated_data, content)
self.dispatch_content(lesson, content)
return lesson
class LessonSerializer(LessonCreateSerializer):
course = CourseSerializer()
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):
base_model = Content
show_in_index = True
base_fieldsets = (
(None, {'fields': ('course', 'lesson', 'title', 'position',)}),
)
@ -56,12 +57,13 @@ class GalleryAdmin(ContentChildAdmin):
@admin.register(Content)
class ContentAdmin(PolymorphicParentModelAdmin):
base_model = Content
polymorphic_list = True
child_models = (
Image,
Text,
ImageText,
Video,
GalleryAdmin,
# GalleryAdmin,
)

@ -40,7 +40,10 @@ class Content(PolymorphicModel):
class Meta:
verbose_name = 'Контент'
verbose_name_plural = 'Контент'
ordering = ('-created_at',)
ordering = ('position', '-created_at',)
def ctype(self):
return self.__class__.__name__.lower()
class Image(Content):
@ -65,6 +68,9 @@ class ImageText(Content):
class Video(Content):
url = models.URLField('Ссылка')
def video_index(self):
return self.url.split('/')[-1]
class Gallery(Content):
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 polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey
from project.mixins import BaseModel, DeactivatedMixin
from .manager import CategoryQuerySet
from apps.content.models import ImageObject, Gallery
@ -25,7 +27,7 @@ def default_slug():
return str(uuid4())
class Course(models.Model):
class Course(BaseModel, DeactivatedMixin):
PENDING = 0
PUBLISHED = 1
ARCHIVED = 2
@ -141,7 +143,7 @@ class Category(models.Model):
ordering = ['title']
class Lesson(models.Model):
class Lesson(BaseModel, DeactivatedMixin):
title = models.CharField('Название урока', max_length=100)
short_description = models.TextField('Краткое описание урока')
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 rupluralize from plural %}
{% block title %}{{ course.title }} - {{ block.super }}{% endblock title %}
{% block description %}{{ course.title }} - {{ block.super }}{% endblock description%}
{% block content %}
<div class="section section_border">
<div class="section__center center center_sm">
@ -195,6 +196,15 @@
</div>
{% endif %}
</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 %}
<div class="section">
<div class="section__center center center_sm">
@ -254,45 +264,7 @@
{% endif %}
<div class="section section_gradient">
<div class="section__center center center_sm">
<div class="title">Галерея итогов обучения</div>
<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>
{% include "course/content/gallery.html" with results=True %}
</div>
</div>
<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 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="title">Примеры техники</div>
<div class="examples">
@ -77,7 +86,7 @@
</div>
</div>
</div>
</div>
</div> {% endcomment %}
<div class="section section_gray">
<div class="section__center center center_sm">
<div class="title">Задавайте вопросы:</div>

@ -1,8 +1,9 @@
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
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.http import require_http_methods
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):
model = Course
context_object_name = 'course'
@ -146,6 +160,14 @@ class CourseView(DetailView):
context['next'] = self.request.GET.get('next', None)
return context
def get_queryset(self):
queryset = super().get_queryset().select_related(
'author', 'category',
).prefetch_related(
'likes', 'materials', 'content',
)
return queryset
class CoursesView(ListView):
model = Course
@ -158,12 +180,17 @@ class CoursesView(ListView):
if request.is_ajax():
context = self.get_context_data()
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')
if is_paginated:
page_obj = context.get('page_obj')
prev_url = request.path + '?page=' + 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
prev_url = request.path + '?page=' + \
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:
prev_url = None
next_url = None
@ -178,7 +205,7 @@ class CoursesView(ListView):
def get_queryset(self):
queryset = super().get_queryset().select_related(
'author', 'category'
'author', 'category', 'cover',
).prefetch_related(
'likes', 'materials', 'content',
)
@ -210,7 +237,8 @@ class SearchView(CoursesView):
search_query = self.request.GET.get('q', None)
queryset = super().get_queryset()
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)
else:
queryset = queryset.none()
@ -223,7 +251,8 @@ class SearchView(CoursesView):
if 'is_paginated' in context and context['is_paginated']:
page_obj = context.get('page_obj')
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:
context['page'] = 1
context['next_page'] = None

@ -26,7 +26,7 @@
"vkontakte": null,
"fb_id": null,
"fb_data": {},
"is_email_proved": false,
"is_email_proved": true,
"photo": "",
"groups": [],
"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.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser, UserManager
from django.contrib.postgres import fields as pgfields
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):
USER_ROLE = 0
@ -22,8 +29,10 @@ class User(AbstractUser):
(FEMALE, 'Женщина'),
)
email = models.EmailField(_('email address'), unique=True)
role = models.PositiveSmallIntegerField('Роль', default=0, choices=ROLE_CHOICES)
gender = models.CharField('Пол', max_length=1, default='n', choices=GENDER_CHOICES)
role = models.PositiveSmallIntegerField(
'Роль', default=0, choices=ROLE_CHOICES)
gender = models.CharField(
'Пол', max_length=1, default='n', choices=GENDER_CHOICES)
birthday = models.DateField('День рождения', null=True, blank=True)
country = models.CharField('Страна', max_length=50, 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)
fb_id = models.BigIntegerField(null=True, blank=True, unique=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')
USERNAME_FIELD = 'email'
@ -44,3 +55,23 @@ class User(AbstractUser):
class Meta(AbstractUser.Meta):
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="theme-color" content="#fff">
<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-->
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@publisher_handle">
@ -135,7 +135,14 @@
{% endif %}
<div class="header__drop">
{% 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="#">
{% else %}
<a class="header__link header__link_gray disabled" href="#">
{% endif %}
{% endwith %}
<div class="header__title">ДОБАВИТЬ КУРС</div>
</a>
<a class="header__link" href="{% url 'user' request.user.id %}">
@ -158,9 +165,11 @@
{% endif %}
</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>
{% endif %}
{% endif %}
</header>
<div id="lilcity-vue-app" class="container">
{% block content %}{% endblock content %}

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

@ -473,6 +473,10 @@ a.btn
color: $green-light
&:hover
color: darken($green-light,10)
&_gray
color: $gray
+m
color: $gray
&_border
margin-bottom: 5px
padding: 2px 20px 10px
@ -3268,9 +3272,19 @@ a.grey-link
a
&.link--black
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