Merge branch 'feature/api' of gitlab.com:lilcity/backend into feature/create-edit-courses

remotes/origin/hasaccess
Vitaly Baev 8 years ago
commit aa5a820ce7
  1. 45
      api/v1/auth.py
  2. 10
      api/v1/serializers/content.py
  3. 12
      api/v1/serializers/course.py
  4. 4
      api/v1/urls.py
  5. 1
      apps/course/admin.py
  6. 11
      apps/course/fixtures/course.json
  7. 17
      apps/course/migrations/0027_remove_course_url.py
  8. 19
      apps/course/migrations/0028_course_slug.py
  9. 19
      apps/course/migrations/0029_auto_20180209_0911.py
  10. 53
      apps/course/models.py
  11. 2
      apps/user/fixtures/superuser.json
  12. 1
      project/urls.py

@ -0,0 +1,45 @@
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.compat import authenticate
User = get_user_model()
class AuthTokenSerializer(serializers.Serializer):
email = serializers.CharField(label=_("Email"))
password = serializers.CharField(
label=_("Password"),
style={'input_type': 'password'},
trim_whitespace=False
)
def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
if email and password:
user = authenticate(request=self.context.get('request'),
email=email, password=password)
# The authenticate call simply returns None for is_active=False
# users. (Assuming the default ModelBackend authentication
# backend.)
if not user:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
elif user.role != User.ADMIN_ROLE:
msg = _('Only admin have permission to login admin page.')
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Must include "email" and "password".')
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs
class ObtainToken(ObtainAuthToken):
serializer_class = AuthTokenSerializer

@ -6,7 +6,6 @@ from apps.content.models import (
)
from . import Base64ImageField
# from .course import CourseSerializer
class ContentCreateSerializer(serializers.Serializer):
@ -156,6 +155,15 @@ class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = (
'id',
'course',
'lesson',
'title',
'position',
'created_at',
'update_at',
)
def to_representation(self, obj):
if isinstance(obj, Image):

@ -65,13 +65,15 @@ class CategorySerializer(serializers.ModelSerializer):
class CourseCreateSerializer(serializers.ModelSerializer):
content = serializers.ListSerializer(child=ContentCreateSerializer())
materials = MaterialSerializer(many=True)
slug = serializers.SlugField(allow_unicode=True, required=False)
content = serializers.ListSerializer(child=ContentCreateSerializer(), required=False)
materials = MaterialSerializer(many=True, required=False)
class Meta:
model = Course
fields = (
'id',
'slug',
'author',
'title',
'short_description',
@ -95,14 +97,14 @@ class CourseCreateSerializer(serializers.ModelSerializer):
read_only_fields = (
'id',
# 'content',
'url',
'created_at',
'update_at',
)
def create(self, validated_data):
materials = validated_data.pop('materials')
content = validated_data.pop('content')
materials = validated_data.pop('materials', [])
content = validated_data.pop('content', [])
course = super().create(validated_data)

@ -6,6 +6,7 @@ from rest_framework.routers import DefaultRouter
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from .auth import ObtainToken
from .views import (
CategoryViewSet, CourseViewSet,
MaterialViewSet, LikeViewSet,
@ -47,5 +48,6 @@ urlpatterns = [
path('swagger(<str:format>.json|.yaml)', schema_view.without_ui(cache_timeout=None), name='schema-json'),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
path('', include((router.urls, 'api-root')), name='api-root')
path('api-token-auth/', ObtainToken.as_view(), name='api-token-auth'),
path('', include((router.urls, 'api-root')), name='api-root'),
]

@ -16,6 +16,7 @@ class CourseAdmin(admin.ModelAdmin):
'created_at',
'update_at',
)
prepopulated_fields = {"slug": ("title",)}
@admin.register(Category)

@ -13,7 +13,6 @@
"category": 2,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:04:41.113Z",
"update_at": "2018-01-31T15:03:47.118Z",
@ -35,7 +34,6 @@
"category": 1,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:09:03.437Z",
"update_at": "2018-01-31T15:03:47.115Z",
@ -57,7 +55,6 @@
"category": 9,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:09:03.442Z",
"update_at": "2018-01-31T15:03:47.112Z",
@ -79,7 +76,6 @@
"category": 8,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:09:03.445Z",
"update_at": "2018-01-31T15:03:47.108Z",
@ -101,7 +97,6 @@
"category": 7,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:09:03.449Z",
"update_at": "2018-01-31T15:03:47.104Z",
@ -123,7 +118,6 @@
"category": 6,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:09:03.452Z",
"update_at": "2018-01-31T15:03:47.101Z",
@ -145,7 +139,6 @@
"category": 5,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:09:03.455Z",
"update_at": "2018-01-31T15:03:47.097Z",
@ -167,7 +160,6 @@
"category": 4,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:09:03.458Z",
"update_at": "2018-01-31T15:03:47.093Z",
@ -189,7 +181,6 @@
"category": 3,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 0,
"created_at": "2018-01-27T07:09:03.461Z",
"update_at": "2018-01-31T15:03:47.089Z",
@ -211,7 +202,6 @@
"category": 2,
"duration": 1,
"is_featured": true,
"url": "https://gitlab.com/",
"status": 1,
"created_at": "2018-01-27T07:09:03.464Z",
"update_at": "2018-01-31T15:03:47.086Z",
@ -237,7 +227,6 @@
"category": 1,
"duration": 1,
"is_featured": false,
"url": "https://gitlab.com/",
"status": 1,
"created_at": "2018-01-27T07:09:03.467Z",
"update_at": "2018-01-31T15:03:47.080Z",

@ -0,0 +1,17 @@
# Generated by Django 2.0.2 on 2018-02-09 08:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('course', '0026_auto_20180208_1053'),
]
operations = [
migrations.RemoveField(
model_name='course',
name='url',
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.2 on 2018-02-09 08:59
from uuid import uuid4
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0027_remove_course_url'),
]
operations = [
migrations.AddField(
model_name='course',
name='slug',
field=models.SlugField(allow_unicode=True, default=str(uuid4()), max_length=100, unique=True),
preserve_default=False,
),
]

@ -0,0 +1,19 @@
# Generated by Django 2.0.2 on 2018-02-09 09:11
import apps.course.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0028_course_slug'),
]
operations = [
migrations.AlterField(
model_name='course',
name='slug',
field=models.SlugField(allow_unicode=True, default=apps.course.models.default_slug, max_length=100, unique=True),
),
]

@ -1,8 +1,10 @@
import arrow
from uuid import uuid4
from django.db import models
from django.utils import timezone
from django.utils.text import slugify
from django.contrib.auth import get_user_model
from django.urls import reverse_lazy
from polymorphic_tree.models import PolymorphicMPTTModel, PolymorphicTreeForeignKey
from .manager import CategoryQuerySet
@ -19,6 +21,10 @@ class Like(models.Model):
update_at = models.DateTimeField(auto_now=True)
def default_slug():
return str(uuid4())
class Course(models.Model):
PENDING = 0
PUBLISHED = 1
@ -28,10 +34,17 @@ class Course(models.Model):
(PUBLISHED, 'Published'),
(ARCHIVED, 'Archived'),
)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
slug = models.SlugField(
allow_unicode=True, default=default_slug,
max_length=100, unique=True, db_index=True,
)
author = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True)
title = models.CharField('Название курса', max_length=100, db_index=True)
short_description = models.TextField('Краткое описание курса', db_index=True)
from_author = models.TextField('От автора', default='', null=True, blank=True)
short_description = models.TextField(
'Краткое описание курса', db_index=True)
from_author = models.TextField(
'От автора', default='', null=True, blank=True)
cover = models.ForeignKey(
ImageObject, related_name='course_covers',
verbose_name='Обложка курса', on_delete=models.CASCADE,
@ -49,8 +62,8 @@ class Course(models.Model):
category = models.ForeignKey('Category', on_delete=models.PROTECT)
duration = models.IntegerField('Продолжительность курса', default=0)
is_featured = models.BooleanField(default=False)
url = models.URLField('Ссылка', default='')
status = models.PositiveSmallIntegerField('Статус', default=0, choices=STATUS_CHOICES)
status = models.PositiveSmallIntegerField(
'Статус', default=0, choices=STATUS_CHOICES)
likes = models.ManyToManyField(Like, blank=True)
materials = models.ManyToManyField('Material', blank=True)
gallery = models.ForeignKey(
@ -61,6 +74,25 @@ class Course(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(
self.title[:100],
allow_unicode=True
)
if Course.objects.filter(slug=self.slug).exists():
self.slug += '_' + str(uuid4())[:6]
return super().save()
@property
def url(self):
return self.get_absolute_url()
def get_absolute_url(self):
return reverse_lazy('course', args=[self.id])
@property
def is_free(self):
if self.price:
@ -108,7 +140,8 @@ class Category(models.Model):
class Lesson(models.Model):
title = models.CharField('Название урока', max_length=100)
short_description = models.TextField('Краткое описание урока')
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons')
course = models.ForeignKey(
Course, on_delete=models.CASCADE, related_name='lessons')
cover = models.ForeignKey(
ImageObject, related_name='lesson_covers',
verbose_name='Обложка урока', on_delete=models.CASCADE,
@ -173,7 +206,8 @@ class Comment(PolymorphicMPTTModel):
class CourseComment(Comment):
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='comments')
course = models.ForeignKey(
Course, on_delete=models.CASCADE, related_name='comments')
class Meta(Comment.Meta):
verbose_name = 'Комментарий курса'
@ -181,7 +215,8 @@ class CourseComment(Comment):
class LessonComment(Comment):
lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, related_name='comments')
lesson = models.ForeignKey(
Lesson, on_delete=models.CASCADE, related_name='comments')
class Meta(Comment.Meta):
verbose_name = 'Комментарий урока'

@ -13,7 +13,7 @@
"is_active": true,
"date_joined": "2018-01-28T08:41:19Z",
"email": "admin@lil.city",
"role": 0,
"role": 2,
"gender": "n",
"country": "",
"city": "",

@ -31,6 +31,7 @@ urlpatterns = [
path('auth/', include(('apps.auth.urls', 'lilcity'))),
path('courses/', CoursesView.as_view(), name='courses'),
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:course_id>/comment', coursecomment, name='coursecomment'),
path('lesson/<int:pk>/', LessonView.as_view(), name='lesson'),

Loading…
Cancel
Save