diff --git a/access/admin.py b/access/admin.py index 5526ca6..7693fe0 100755 --- a/access/admin.py +++ b/access/admin.py @@ -1,9 +1,12 @@ from django.contrib import admin -from access.models.other import Invite, Account, Progress, ResetPassword +from access.models.other import Invite, Account, Progress, ResetPassword, ProgressLesson, UserAnswer, UserAnswerBlock from access.models.user import User admin.site.register(User) admin.site.register(Account) admin.site.register(Progress) admin.site.register(Invite) -admin.site.register(ResetPassword) \ No newline at end of file +admin.site.register(ResetPassword) +admin.site.register(ProgressLesson) +admin.site.register(UserAnswer) +admin.site.register(UserAnswerBlock) \ No newline at end of file diff --git a/access/migrations/0001_initial.py b/access/migrations/0001_initial.py index c70af12..790b95d 100644 --- a/access/migrations/0001_initial.py +++ b/access/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:03 +# Generated by Django 1.11.6 on 2018-01-11 13:07 from __future__ import unicode_literals import access.models.user @@ -8,6 +8,7 @@ import django.contrib.postgres.fields from django.db import migrations, models import django.db.models.deletion import phonenumber_field.modelfields +import uuid class Migration(migrations.Migration): @@ -22,11 +23,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='User', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('out_key', models.CharField(editable=False, max_length=15, unique=True, verbose_name='Ключ для внешних сервисов')), + ('out_key', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='Токен')), ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), ('first_name', models.CharField(blank=True, default='Гость', max_length=63, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=63, verbose_name='last name')), @@ -53,7 +53,7 @@ class Migration(migrations.Migration): ('city', models.CharField(blank=True, max_length=63, null=True)), ('gender', models.SmallIntegerField(choices=[(0, 'undefined'), (1, 'male'), (2, 'female')], default=0)), ('photo', models.ImageField(default='/static/default/access/default.png', upload_to='user/photo/')), - ('phone', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128)), + ('phone', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True)), ('owner', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ @@ -73,20 +73,13 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Приглошения в систему', }, ), - migrations.CreateModel( - name='PivotProgressVertex', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('vertex', models.CharField(blank=True, max_length=15, unique=True, verbose_name='Ссылки на узлы')), - ('status', models.SmallIntegerField(choices=[(2, 'Выполненно'), (1, 'Ожидание'), (0, 'Не выполненно')], default=0)), - ('comment', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=15, unique=True, verbose_name='Ссылки на комменты'), size=None)), - ], - ), migrations.CreateModel( name='Progress', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('route', models.CharField(max_length=15, verbose_name='Токен прохождения')), + ('lessons', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=15, unique=True, verbose_name='Ссылки на скрытые уроки'), default=[], size=None)), + ('route', models.CharField(blank=True, max_length=15, null=True, verbose_name='Токен прохождения')), + ('course', models.CharField(max_length=127, verbose_name='Слаг курса')), ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teacher_progress', to=settings.AUTH_USER_MODEL, verbose_name='Преподователь по умолчанию')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Студент')), ], @@ -95,6 +88,48 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Прогресс пользователя', }, ), + migrations.CreateModel( + name='ProgressLesson', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('lesson', models.CharField(blank=True, max_length=15, verbose_name='Ссылка на узлы')), + ('comment', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=15, unique=True, verbose_name='Ссылки на комменты'), default=[], size=None)), + ('date', models.DateTimeField(blank=True, null=True, verbose_name='Дата зачтения задания')), + ('progress', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.Progress')), + ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Преподователь')), + ], + options={ + 'verbose_name': 'Прохождение уроков', + 'verbose_name_plural': 'Прохождение урока', + }, + ), + migrations.CreateModel( + name='UserAnswer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(blank=True, max_length=255, verbose_name='Наименование поля')), + ('value', models.TextField(verbose_name='Значение в поле')), + ('status', models.CharField(choices=[('done', 'done'), ('wait', 'wait'), ('fail', 'fail')], default='done', max_length=20)), + ], + options={ + 'verbose_name': 'Ответ пользователя', + 'verbose_name_plural': 'Ответы пользователя', + }, + ), + migrations.CreateModel( + name='UserAnswerBlock', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateTimeField(auto_now_add=True, verbose_name='Дата сдачи')), + ('demand', models.CharField(blank=True, max_length=15, verbose_name='Ссылка на правило')), + ('checker', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Проверяющий')), + ('progress', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.ProgressLesson')), + ], + options={ + 'verbose_name': 'Блок ответов пользователя', + 'verbose_name_plural': 'Блоки ответов пользователя', + }, + ), migrations.CreateModel( name='ResetPassword', fields=[ @@ -108,14 +143,9 @@ class Migration(migrations.Migration): bases=('access.invite',), ), migrations.AddField( - model_name='pivotprogressvertex', - name='progress', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.Progress'), - ), - migrations.AddField( - model_name='pivotprogressvertex', - name='teacher', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Преподователь'), + model_name='useranswer', + name='block', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.UserAnswerBlock'), ), migrations.AddField( model_name='invite', diff --git a/access/migrations/0002_auto_20171217_1808.py b/access/migrations/0002_auto_20171217_1808.py deleted file mode 100644 index 8c0c049..0000000 --- a/access/migrations/0002_auto_20171217_1808.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:08 -from __future__ import unicode_literals - -from django.db import migrations -import phonenumber_field.modelfields - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='account', - name='phone', - field=phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True), - ), - ] diff --git a/access/init_group.py b/access/migrations/0002_init_group.py similarity index 57% rename from access/init_group.py rename to access/migrations/0002_init_group.py index c191289..fd03861 100644 --- a/access/init_group.py +++ b/access/migrations/0002_init_group.py @@ -1,14 +1,11 @@ -import os, sys, django +from __future__ import unicode_literals -sys.path.append("../") -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.settings") -django.setup() +from django.contrib.auth.models import Group -# TODO переместить в миграции +from django.db import migrations -from django.contrib.auth.models import Group -if __name__ == '__main__': +def init_group(*_args, **_kwargs): Group.objects.get_or_create(name='admin') Group.objects.get_or_create(name='students') Group.objects.get_or_create(name='teachers') @@ -18,4 +15,17 @@ if __name__ == '__main__': Group.objects.get_or_create(name='partners') Group.objects.get_or_create(name='supports') Group.objects.get_or_create(name='finance') - Group.objects.get_or_create(name='project_managers') \ No newline at end of file + Group.objects.get_or_create(name='project_managers') + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('access', '0001_initial'), + ] + + operations = [ + migrations.RunPython(init_group) + ] diff --git a/access/migrations/0003_pivotprogressvertex_date.py b/access/migrations/0003_pivotprogressvertex_date.py deleted file mode 100644 index c0ade01..0000000 --- a/access/migrations/0003_pivotprogressvertex_date.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-18 14:37 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0002_auto_20171217_1808'), - ] - - operations = [ - migrations.AddField( - model_name='pivotprogressvertex', - name='date', - field=models.DateTimeField(blank=True, null=True, verbose_name='Дата зачтения задания'), - ), - ] diff --git a/access/migrations/0004_auto_20171218_1438.py b/access/migrations/0004_auto_20171218_1438.py deleted file mode 100644 index 6c0690d..0000000 --- a/access/migrations/0004_auto_20171218_1438.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-18 14:38 -from __future__ import unicode_literals - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0003_pivotprogressvertex_date'), - ] - - operations = [ - migrations.AlterField( - model_name='pivotprogressvertex', - name='comment', - field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=15, unique=True, verbose_name='Ссылки на комменты'), default=[], size=None), - ), - ] diff --git a/access/migrations/0005_auto_20171218_1516.py b/access/migrations/0005_auto_20171218_1516.py deleted file mode 100644 index b0e9808..0000000 --- a/access/migrations/0005_auto_20171218_1516.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-18 15:16 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0004_auto_20171218_1438'), - ] - - operations = [ - migrations.AlterField( - model_name='pivotprogressvertex', - name='vertex', - field=models.CharField(blank=True, max_length=15, verbose_name='Ссылки на узлы'), - ), - ] diff --git a/access/migrations/0006_auto_20171220_1228.py b/access/migrations/0006_auto_20171220_1228.py deleted file mode 100644 index 103062c..0000000 --- a/access/migrations/0006_auto_20171220_1228.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-20 12:28 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0005_auto_20171218_1516'), - ] - - operations = [ - migrations.AlterField( - model_name='progress', - name='route', - field=models.CharField(blank=True, max_length=15, null=True, verbose_name='Токен прохождения'), - ), - ] diff --git a/access/migrations/0007_progress_course.py b/access/migrations/0007_progress_course.py deleted file mode 100644 index 2ce063c..0000000 --- a/access/migrations/0007_progress_course.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-20 13:21 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0006_auto_20171220_1228'), - ] - - operations = [ - migrations.AddField( - model_name='progress', - name='course', - field=models.CharField(default='', max_length=15, verbose_name='Токен курса'), - preserve_default=False, - ), - ] diff --git a/access/migrations/0008_auto_20171220_1325.py b/access/migrations/0008_auto_20171220_1325.py deleted file mode 100644 index c0c1628..0000000 --- a/access/migrations/0008_auto_20171220_1325.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-20 13:25 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0007_progress_course'), - ] - - operations = [ - migrations.AlterField( - model_name='progress', - name='course', - field=models.CharField(max_length=127, verbose_name='Слаг курса'), - ), - ] diff --git a/access/models/other.py b/access/models/other.py index 73ab22d..cc6716e 100644 --- a/access/models/other.py +++ b/access/models/other.py @@ -1,6 +1,7 @@ from django.conf import settings from django.contrib.postgres.fields import ArrayField from django.db import models +from model_utils import Choices from phonenumber_field.modelfields import PhoneNumberField @@ -44,6 +45,8 @@ class Account(models.Model): class Progress(models.Model): + lessons = ArrayField( + models.CharField(max_length=15, verbose_name='Ссылки на скрытые уроки', unique=True), default=[]) teacher = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Преподователь по умолчанию", related_name='teacher_progress') user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Студент') @@ -59,16 +62,36 @@ class Progress(models.Model): unique_together = (("user", "route"),) -class PivotProgressVertex(models.Model): - # TODO переименовать в ProgressVertex - VERTEX_STATUS = ( - (2, 'Выполненно'), - (1, 'Ожидание'), - (0, 'Не выполненно'), - ) # TODO перейти на Choices из django-model-utils и сделать ключи строками done/wait/fail +class ProgressLesson(models.Model): progress = models.ForeignKey(to=Progress) - vertex = models.CharField(max_length=15, blank=True, verbose_name='Ссылки на узлы') - status = models.SmallIntegerField(choices=VERTEX_STATUS, default=0) + lesson = models.CharField(max_length=15, blank=True, verbose_name='Ссылка на узлы') teacher = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Преподователь",) comment = ArrayField(models.CharField(max_length=15, verbose_name='Ссылки на комменты', unique=True), default=[]) date = models.DateTimeField(verbose_name='Дата зачтения задания', blank=True, null=True) + + class Meta: + verbose_name = 'Прохождение уроков' + verbose_name_plural = 'Прохождение урока' + + +class UserAnswerBlock(models.Model): + progress = models.ForeignKey(to=ProgressLesson) + date = models.DateTimeField(verbose_name='Дата сдачи', auto_now_add=True) + demand = models.CharField(max_length=15, blank=True, verbose_name='Ссылка на правило') + checker = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Проверяющий",) + + class Meta: + verbose_name = 'Блок ответов пользователя' + verbose_name_plural = 'Блоки ответов пользователя' + + +class UserAnswer(models.Model): + block = models.ForeignKey(to=UserAnswerBlock) + key = models.CharField(max_length=255, blank=True, verbose_name='Наименование поля') + value = models.TextField(verbose_name='Значение в поле') + STATUS = Choices('done', 'wait', 'fail') + status = models.CharField(choices=STATUS, default=STATUS.done, max_length=20) + + class Meta: + verbose_name = 'Ответ пользователя' + verbose_name_plural = 'Ответы пользователя' diff --git a/course_service/__init__.py b/access/models/progress.py similarity index 100% rename from course_service/__init__.py rename to access/models/progress.py diff --git a/access/models/user.py b/access/models/user.py index 71e993d..449f932 100644 --- a/access/models/user.py +++ b/access/models/user.py @@ -1,5 +1,6 @@ import random import string +import uuid from django.contrib.auth.base_user import BaseUserManager, AbstractBaseUser from django.contrib.auth.models import Group, PermissionsMixin @@ -44,7 +45,6 @@ class CustomUserManager(BaseUserManager): email = self.normalize_email(email) user = self.model(email=email, is_staff=is_staff, is_active=is_active, first_name=first_name, - out_key=''.join(random.choice(string.ascii_letters) for x in range(15)), is_superuser=is_superuser, date_joined=date_joined, last_login=last_login, **extra_fields) if not password: @@ -93,7 +93,7 @@ class CustomUserManager(BaseUserManager): class User(AbstractBaseUser, PermissionsMixin): - out_key = models.CharField(max_length=15, unique=True, verbose_name="Ключ для внешних сервисов", editable=False) + out_key = models.UUIDField(verbose_name="Токен", default=uuid.uuid4, primary_key=True, editable=False) email = models.EmailField(_('email address'), unique=True) first_name = models.CharField(_('first name'), max_length=63, blank=True, default='Гость') last_name = models.CharField(_('last name'), max_length=63, blank=True) diff --git a/access/serializers.py b/access/serializers.py index bcba9c8..1d9e1cd 100644 --- a/access/serializers.py +++ b/access/serializers.py @@ -1,23 +1,17 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from access.models.other import Account, Progress, PivotProgressVertex +from access.models.other import Account, Progress, ProgressLesson from achievements.serialers import DiplomaSerializer, AchievementsSerializer -class PivotProgressSerializer(serializers.ModelSerializer): - # TODO переименовать в ProgressVertexSerializer - status = serializers.SerializerMethodField() +class ProgressVertexSerializer(serializers.ModelSerializer): teacher = serializers.SerializerMethodField() class Meta: - model = PivotProgressVertex + model = ProgressLesson exclude = ('id', 'progress') - @staticmethod - def get_status(self): - return self.get_status_display() - @staticmethod def get_teacher(self): return self.teacher.get_full_name() @@ -32,7 +26,7 @@ class ProgressSerializer(serializers.ModelSerializer): @staticmethod def get_vertexes(self): - return [PivotProgressSerializer(i).data for i in self.pivotprogressvertex_set.all()] + return [ProgressVertexSerializer(i).data for i in self.progressvertex_set.all()] class AccountSerializer(serializers.ModelSerializer): diff --git a/access/views.py b/access/views.py index b72f3de..31ff8ec 100644 --- a/access/views.py +++ b/access/views.py @@ -12,8 +12,8 @@ from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView -from access.models.other import Invite, Progress, ResetPassword, PivotProgressVertex -from access.serializers import UserSelfSerializer, UserSearchSerializer, PivotProgressSerializer +from access.models.other import Invite, Progress, ResetPassword, ProgressLesson +from access.serializers import UserSelfSerializer, UserSearchSerializer, ProgressVertexSerializer class TeacherListView(APIView): @@ -244,13 +244,13 @@ class UpdateProgress(APIView): try: p = Progress.objects.get(user=request.user, course=course) try: - pv = PivotProgressVertex.objects.get( + pv = ProgressLesson.objects.get( progress=p, vertex=token, ) - except PivotProgressVertex.DoesNotExist: - pv = PivotProgressVertex.objects.create( + except ProgressLesson.DoesNotExist: + pv = ProgressLesson.objects.create( date=datetime.datetime.now(), teacher=p.teacher, progress=p, @@ -258,7 +258,7 @@ class UpdateProgress(APIView): ) pv.status = 2 pv.save() - return Response(PivotProgressSerializer(pv).data, status=200) + return Response(ProgressVertexSerializer(pv).data, status=200) except Progress.DoesNotExist: return Response('Не найден прогресс по заданным параметрам', status=404) diff --git a/achievements/admin.py b/achievements/admin.py index 55e989a..3cf1b8d 100644 --- a/achievements/admin.py +++ b/achievements/admin.py @@ -1,9 +1,9 @@ from django.contrib import admin -from achievements.models import Skills, Achievements, SkillJ, DiplomaGen, Diploma +from achievements.models import Skill, Achievement, SkillJ, DiplomaGen, Diploma -admin.site.register(Skills) -admin.site.register(Achievements) +admin.site.register(Skill) +admin.site.register(Achievement) admin.site.register(SkillJ) admin.site.register(Diploma) admin.site.register(DiplomaGen) diff --git a/achievements/migrations/0001_initial.py b/achievements/migrations/0001_initial.py index a7f8722..f5e4d9e 100644 --- a/achievements/migrations/0001_initial.py +++ b/achievements/migrations/0001_initial.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:03 +# Generated by Django 1.11.6 on 2018-01-11 10:47 from __future__ import unicode_literals +from django.conf import settings from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -10,15 +12,18 @@ class Migration(migrations.Migration): initial = True dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('courses', '0001_initial'), ] operations = [ migrations.CreateModel( - name='Achievements', + name='Achievement', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('img', models.ImageField(upload_to='achives', verbose_name='Отображение достижения')), ('text', models.CharField(max_length=255, verbose_name='Текст достижения')), + ('users', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), ], options={ 'verbose_name': 'Достижение', @@ -41,6 +46,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('template', models.URLField(verbose_name='Путь до шаблона')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Course')), ], options={ 'verbose_name': 'Генератор дипломов', @@ -48,27 +54,39 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='SkillJ', + name='Skill', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255, verbose_name='Наименование')), + ('color', models.CharField(max_length=255, verbose_name='Цвет')), + ('icon', models.ImageField(help_text='65x65', upload_to='skills', verbose_name='Картинка')), + ('description', models.TextField(blank=True, verbose_name='Описание')), ], options={ - 'verbose_name': 'Размер навыка', - 'verbose_name_plural': 'Размеры навыков', + 'verbose_name': 'Навык', + 'verbose_name_plural': 'Навыки', }, ), migrations.CreateModel( - name='Skills', + name='SkillJ', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=255, verbose_name='Наименование')), - ('color', models.CharField(max_length=255, verbose_name='Цвет')), - ('icon', models.ImageField(help_text='65x65', upload_to='skills', verbose_name='Картинка')), - ('description', models.TextField(blank=True, verbose_name='Описание')), + ('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Lesson', verbose_name='Урок')), + ('skill', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='achievements.Skill', verbose_name='Навык')), ], options={ - 'verbose_name': 'Навык', - 'verbose_name_plural': 'Навыки', + 'verbose_name': 'Размер навыка', + 'verbose_name_plural': 'Размеры навыков', }, ), + migrations.AddField( + model_name='diploma', + name='template', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='achievements.DiplomaGen', verbose_name='Использовать шаблон'), + ), + migrations.AddField( + model_name='diploma', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), ] diff --git a/achievements/models.py b/achievements/models.py index 2625bfb..7a30517 100644 --- a/achievements/models.py +++ b/achievements/models.py @@ -1,10 +1,10 @@ from django.conf import settings from django.db import models -from course_service.courses.models import Course, Vertex +from courses.models import Course, Lesson -class Achievements(models.Model): +class Achievement(models.Model): img = models.ImageField(verbose_name='Отображение достижения', upload_to='achives') text = models.CharField(max_length=255, verbose_name="Текст достижения") users = models.ManyToManyField(to=settings.AUTH_USER_MODEL) @@ -14,7 +14,7 @@ class Achievements(models.Model): verbose_name_plural = 'Достижения' -class Skills(models.Model): +class Skill(models.Model): title = models.CharField(verbose_name='Наименование', max_length=255) color = models.CharField(verbose_name='Цвет', max_length=255) icon = models.ImageField(verbose_name='Картинка', upload_to='skills', help_text='65x65') @@ -28,8 +28,8 @@ class Skills(models.Model): class SkillJ(models.Model): - skill = models.ForeignKey(to=Skills, verbose_name='Навык') - lesson = models.ForeignKey(to=Vertex, verbose_name='Урок') + skill = models.ForeignKey(to=Skill, verbose_name='Навык') + lesson = models.ForeignKey(to=Lesson, verbose_name='Урок') def __str__(self): return '%s' % self.skill diff --git a/achievements/serialers.py b/achievements/serialers.py index bca7ab2..e3b00eb 100644 --- a/achievements/serialers.py +++ b/achievements/serialers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from achievements.models import Diploma, Achievements +from achievements.models import Diploma, Achievement class DiplomaSerializer(serializers.ModelSerializer): @@ -12,5 +12,5 @@ class DiplomaSerializer(serializers.ModelSerializer): class AchievementsSerializer(serializers.ModelSerializer): class Meta: - model = Achievements + model = Achievement fields = ('img', 'text') \ No newline at end of file diff --git a/api_v1/urls.py b/api_v1/urls.py index 3ec2478..0208884 100644 --- a/api_v1/urls.py +++ b/api_v1/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url, include urlpatterns = [ - url(r'courses/', include('course_service.courses.urls')), + url(r'courses/', include('courses.urls')), url(r'users/', include('access.urls')), url(r'library/', include('library.urls')), url(r'finance/', include('finance.urls')), diff --git a/course_service/courses/__init__.py b/course_service/courses/__init__.py deleted file mode 100755 index a85b23a..0000000 --- a/course_service/courses/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = "course_service.courses.apps.CoursesAppConfig" diff --git a/course_service/courses/admin.py b/course_service/courses/admin.py deleted file mode 100755 index d35da92..0000000 --- a/course_service/courses/admin.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.contrib import admin - -from course_service.courses.models import Course, Topic, Vertex - -admin.site.register(Topic) -admin.site.register(Vertex) -admin.site.register(Course) \ No newline at end of file diff --git a/course_service/courses/migrations/0001_initial.py b/course_service/courses/migrations/0001_initial.py deleted file mode 100644 index c9eaa46..0000000 --- a/course_service/courses/migrations/0001_initial.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:03 -from __future__ import unicode_literals - -import django.contrib.postgres.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Course', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('hidden', models.BooleanField(default=False, verbose_name='Видно только оплатившим')), - ('level', models.CharField(choices=[('B', 'Базовый'), ('A', 'Продвинутый'), ('E', 'Экспертный'), ('B+A', 'Базовый + Продвинутый')], default='B', max_length=3, verbose_name='Уровень')), - ('slug', models.SlugField(editable=False, unique=True)), - ('direction', models.SmallIntegerField(choices=[(3, 'Бизнес'), (2, 'Веб-дизайн'), (1, 'Разработка'), (4, 'Рисование')], verbose_name='Направление')), - ('public', models.BooleanField(default=False, verbose_name='Опубликовать')), - ('title', models.CharField(max_length=255, unique=True, verbose_name='Заголовок')), - ('description', models.TextField(blank=True, verbose_name='Описание')), - ('image', models.URLField(blank=True, max_length=255, verbose_name='Изображение')), - ('big_image', models.URLField(blank=True, max_length=255, verbose_name='Большое изображение')), - ('big_mobile_image', models.URLField(blank=True, help_text='Большая картинка для мобильной версии', max_length=255, verbose_name='Под мобилку')), - ('teachers', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=15, verbose_name='Ссылки на преподов'), default=[], size=None)), - ], - options={ - 'verbose_name': 'Курс', - 'verbose_name_plural': 'Курсы', - }, - ), - migrations.CreateModel( - name='Topic', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=255, verbose_name='Название')), - ('icon', models.ImageField(blank=True, null=True, upload_to='', verbose_name='Иконка темы')), - ('description', models.TextField(blank=True, null=True, verbose_name='Описание')), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Course', verbose_name='курс')), - ], - options={ - 'verbose_name': 'Тема', - 'verbose_name_plural': 'Темы', - }, - ), - migrations.CreateModel( - name='Vertex', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=255, verbose_name='Название')), - ('free', models.BooleanField(default=False, verbose_name='Привилегии для узла не будут проверяться')), - ('description', models.TextField(blank=True, null=True, verbose_name='Описание')), - ('video', models.TextField(blank=True, null=True, verbose_name='Код видео')), - ('materials', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=15, verbose_name='Материалы урока'), size=None)), - ('valid_type', models.SmallIntegerField(choices=[(3, 'Автаматическая валидация'), (2, 'Полуавтаматическая валидация'), (1, 'Ручная валидация'), (0, 'Без валидации')], default=0)), - ('token', models.CharField(editable=False, max_length=15, unique=True, verbose_name='Ключ доступа к узлу')), - ('topic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Topic', verbose_name='Тема')), - ], - options={ - 'verbose_name': 'Урок', - 'verbose_name_plural': 'Уроки', - }, - ), - ] diff --git a/course_service/courses/migrations/0002_course_route.py b/course_service/courses/migrations/0002_course_route.py deleted file mode 100644 index 2509ad3..0000000 --- a/course_service/courses/migrations/0002_course_route.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:03 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('courses', '0001_initial'), - ('maps', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='course', - name='route', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='maps.CourseRoute', verbose_name='Порядок прохождения по умолчанию'), - ), - ] diff --git a/course_service/courses/migrations/0003_auto_20171217_1821.py b/course_service/courses/migrations/0003_auto_20171217_1821.py deleted file mode 100644 index 0ecde21..0000000 --- a/course_service/courses/migrations/0003_auto_20171217_1821.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:21 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('courses', '0002_course_route'), - ] - - operations = [ - migrations.AlterField( - model_name='course', - name='slug', - field=models.SlugField(editable=False, max_length=127, unique=True), - ), - ] diff --git a/course_service/courses/signals.py b/course_service/courses/signals.py deleted file mode 100644 index 5e86e42..0000000 --- a/course_service/courses/signals.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.db.models.signals import pre_delete -from django.dispatch import receiver - -from course_service.courses.models import Vertex - - -@receiver(pre_delete, sender=Vertex) -def delete_dependencies(instance, **kwargs): - """Удаляем зависимости вместе с узлом""" - if instance.content_object: - instance.content_object.delete() diff --git a/course_service/maps/__init__.py b/course_service/maps/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/course_service/maps/admin.py b/course_service/maps/admin.py deleted file mode 100644 index 36efe2a..0000000 --- a/course_service/maps/admin.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.contrib import admin - -from course_service.maps.models import CourseMap, CourseRoute, PivotCourseMap, PivotVertex - -admin.site.register(CourseMap) -admin.site.register(CourseRoute) -admin.site.register(PivotCourseMap) -admin.site.register(PivotVertex) diff --git a/course_service/maps/api.py b/course_service/maps/api.py deleted file mode 100644 index ef4edf0..0000000 --- a/course_service/maps/api.py +++ /dev/null @@ -1,16 +0,0 @@ -from course_service.maps.models import CourseRoute - - -class OutApiRoute: - @staticmethod - def change_id(id: int) -> str: - return CourseRoute.objects.get(id=id).out_key - - @staticmethod - def get_route_matrix(out_key): - map_list = [i.map_course for i in CourseRoute.objects.get(out_key=out_key).pivotcoursemap_set.all()] - return [[j.vertex.token for j in i.pivotvertex_set.all()] for i in map_list] - - @staticmethod - def get_route(out_key): - return CourseRoute.objects.get(out_key=out_key) \ No newline at end of file diff --git a/course_service/maps/apps.py b/course_service/maps/apps.py deleted file mode 100644 index 76f0dbf..0000000 --- a/course_service/maps/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class MapsConfig(AppConfig): - name = "course_service.maps" - label = 'maps' - verbose_name = "Отображение курсов" diff --git a/course_service/maps/exeptions.py b/course_service/maps/exeptions.py deleted file mode 100644 index 493d171..0000000 --- a/course_service/maps/exeptions.py +++ /dev/null @@ -1,2 +0,0 @@ -class MapTypeError(ValueError): - pass diff --git a/course_service/maps/migrations/0001_initial.py b/course_service/maps/migrations/0001_initial.py deleted file mode 100644 index 2a4cd45..0000000 --- a/course_service/maps/migrations/0001_initial.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:03 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('courses', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='CourseMap', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Имя прохождения')), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Course', verbose_name='К какому курсу привязан')), - ], - options={ - 'verbose_name': 'Карта линейного прохождения курсов', - 'verbose_name_plural': 'Карты линейного прохождения курсов', - }, - ), - migrations.CreateModel( - name='CourseRoute', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('out_key', models.CharField(editable=False, max_length=15, unique=True, verbose_name='Ключ для внешних сервисов')), - ('name', models.CharField(max_length=255, verbose_name='Имя шаблона')), - ('is_template', models.BooleanField(default=True, verbose_name='Может ли быть использован как шаблон')), - ], - options={ - 'verbose_name': 'Маршрут прохождения', - 'verbose_name_plural': 'Маршруты прохождения', - }, - ), - migrations.CreateModel( - name='PivotCourseMap', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sort', models.SmallIntegerField(verbose_name='Порядок сортировки')), - ('map_course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='maps.CourseMap', verbose_name='К какой сортировке имеетотношение')), - ('route', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='maps.CourseRoute', verbose_name='К какому узлу')), - ], - options={ - 'verbose_name': 'Порядок сортировки маршрута', - 'verbose_name_plural': 'Порядки сортировок маршрутов', - 'ordering': ('sort',), - }, - ), - migrations.CreateModel( - name='PivotVertex', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sort', models.SmallIntegerField(verbose_name='Порядок сортировки')), - ('map_course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='maps.CourseMap', verbose_name='К какой сортировке имеетотношение')), - ('vertex', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Vertex', verbose_name='К какому узлу')), - ], - options={ - 'verbose_name': 'Порядок сортировки узла', - 'verbose_name_plural': 'Порядки сортировок узла', - 'ordering': ('sort',), - }, - ), - migrations.AlterUniqueTogether( - name='pivotvertex', - unique_together=set([('sort', 'map_course'), ('map_course', 'vertex')]), - ), - migrations.AlterUniqueTogether( - name='pivotcoursemap', - unique_together=set([('sort', 'route'), ('map_course', 'route')]), - ), - migrations.AlterUniqueTogether( - name='coursemap', - unique_together=set([('course', 'name')]), - ), - ] diff --git a/course_service/maps/migrations/__init__.py b/course_service/maps/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/course_service/maps/models.py b/course_service/maps/models.py deleted file mode 100644 index 3eaddce..0000000 --- a/course_service/maps/models.py +++ /dev/null @@ -1,67 +0,0 @@ -from django.db import models - -from course_service.maps.exeptions import MapTypeError -from lms.global_decorators import transaction_decorator - - -class CourseRoute(models.Model): - """ - Объединение нескольких мап курса, одназначно - определяет способ прохождения по курсу. - """ - out_key = models.CharField(max_length=15, unique=True, verbose_name="Ключ для внешних сервисов", editable=False) - name = models.CharField(max_length=255, verbose_name='Имя шаблона') - is_template = models.BooleanField(default=True, verbose_name='Может ли быть использован как шаблон') - - def __str__(self): - return self.name - - class Meta: - verbose_name = 'Маршрут прохождения' - verbose_name_plural = 'Маршруты прохождения' - - -class CourseMap(models.Model): - """ - Способы отображения курса. Упорядочены в порядке возрастания приоретета. - """ - course = models.ForeignKey(to='courses.Course', verbose_name='К какому курсу привязан') - name = models.CharField(max_length=255, verbose_name="Имя прохождения") - - def __str__(self): - return '''Линейное прохождение по курсу "%s"''' % self.course.title - - class Meta: - verbose_name = 'Карта линейного прохождения курсов' - verbose_name_plural = 'Карты линейного прохождения курсов' - unique_together =('course', 'name',) - - -class PivotCourseMap(models.Model): - route = models.ForeignKey(to=CourseRoute, verbose_name="К какому узлу") - sort = models.SmallIntegerField(verbose_name='Порядок сортировки') - map_course = models.ForeignKey(to=CourseMap, verbose_name='К какой сортировке имеетотношение') - - def __str__(self): - return '''Карта с №%s по маршруту ID%s''' % (self.sort, self.route_id) - - class Meta: - verbose_name = 'Порядок сортировки маршрута' - verbose_name_plural = 'Порядки сортировок маршрутов' - unique_together = (('map_course', 'route'), ('sort', 'route'),) - ordering = ('sort',) - - -class PivotVertex(models.Model): - vertex = models.ForeignKey(to='courses.Vertex', verbose_name="К какому узлу") - sort = models.SmallIntegerField(verbose_name='Порядок сортировки') - map_course = models.ForeignKey(to=CourseMap, verbose_name='К какой сортировке имеетотношение') - - def __str__(self): - return '''Карта с №%s по линейному прохождению ID%s''' % (self.sort, self.map_course_id) - - class Meta: - verbose_name = 'Порядок сортировки узла' - verbose_name_plural = 'Порядки сортировок узла' - unique_together = (('map_course', 'vertex'), ('sort', 'map_course')) - ordering = ('sort',) diff --git a/course_service/maps/serializers.py b/course_service/maps/serializers.py deleted file mode 100644 index 5f928ca..0000000 --- a/course_service/maps/serializers.py +++ /dev/null @@ -1,27 +0,0 @@ -from rest_framework import serializers - -from course_service.maps.models import CourseRoute, CourseMap - - -class CourseRouteSerializer(serializers.ModelSerializer): - maps = serializers.SerializerMethodField() - - class Meta: - model = CourseRoute - fields = ('maps', 'name') - - @staticmethod - def get_maps(self): - return [CourseMapSerializer(i.map_course).data for i in self.pivotcoursemap_set.all()] - - -class CourseMapSerializer(serializers.ModelSerializer): - vertexes = serializers.SerializerMethodField() - - class Meta: - model = CourseMap - fields = ('name', 'vertexes') - - @staticmethod - def get_vertexes(self): - return [i.vertex.token for i in self.pivotvertex_set.all()] diff --git a/course_service/maps/tests.py b/course_service/maps/tests.py deleted file mode 100644 index ac84f6d..0000000 --- a/course_service/maps/tests.py +++ /dev/null @@ -1,23 +0,0 @@ -# from django.test import TestCase -# from course_service.maps.models import CourseRoute -# -# from course_service.maps.api import OutApiRoute -# -# -# class RouteOutApiTestCase(TestCase): -# def setUp(self): -# self.first_course = Course.objects.create( -# title='Первый курс', -# slug='perviy-kuourse', -# ) -# self.CDTeacher = OutApiRoute() -# -# def test_teacher(self): -# token1 = "token1" -# token2 = "token2" -# token3 = "token3" -# self.CDTeacher.add_teacher(slug=self.first_course.slug, token=token1) -# self.CDTeacher.add_teacher(slug=self.first_course.slug, token=token2) -# self.CDTeacher.add_teacher(slug=self.first_course.slug, token=token3) -# self.CDTeacher.delete_teacher(slug=self.first_course.slug, token=token2) -# self.assertEqual(self.CDTeacher.get_token_list(self.first_course.slug), ['token1', 'token3']) diff --git a/course_service/maps/views.py b/course_service/maps/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/course_service/maps/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/courses/__init__.py b/courses/__init__.py new file mode 100755 index 0000000..354e387 --- /dev/null +++ b/courses/__init__.py @@ -0,0 +1 @@ +default_app_config = "courses.apps.CoursesAppConfig" diff --git a/courses/admin.py b/courses/admin.py new file mode 100755 index 0000000..e1016f6 --- /dev/null +++ b/courses/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from courses.models import Course, Topic, Lesson, Demand, Field + +admin.site.register(Topic) +admin.site.register(Lesson) +admin.site.register(Course) +admin.site.register(Demand) +admin.site.register(Field) \ No newline at end of file diff --git a/course_service/courses/api.py b/courses/api.py similarity index 91% rename from course_service/courses/api.py rename to courses/api.py index ba95edd..dd0e851 100644 --- a/course_service/courses/api.py +++ b/courses/api.py @@ -1,4 +1,4 @@ -from course_service.courses.models import Course +from courses.models import Course class InApiTeacher: diff --git a/course_service/courses/apps.py b/courses/apps.py similarity index 78% rename from course_service/courses/apps.py rename to courses/apps.py index db22b5b..fcf2747 100644 --- a/course_service/courses/apps.py +++ b/courses/apps.py @@ -3,7 +3,7 @@ from django.apps import AppConfig class CoursesAppConfig(AppConfig): - name = "course_service.courses" + name = "courses" label = 'courses' verbose_name = "Курсы" diff --git a/courses/migrations/0001_initial.py b/courses/migrations/0001_initial.py new file mode 100644 index 0000000..e5681ca --- /dev/null +++ b/courses/migrations/0001_initial.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2018-01-11 10:47 +from __future__ import unicode_literals + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Course', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField(editable=False, max_length=127, unique=True)), + ('title', models.CharField(max_length=255, unique=True, verbose_name='Заголовок')), + ('description', models.TextField(blank=True, verbose_name='Описание')), + ('level', models.CharField(choices=[('B', 'Базовый'), ('A', 'Продвинутый'), ('E', 'Экспертный'), ('B+A', 'Базовый + Продвинутый')], default='B', max_length=3, verbose_name='Уровень')), + ('direction', models.SmallIntegerField(choices=[(3, 'Бизнес'), (2, 'Веб-дизайн'), (1, 'Разработка'), (4, 'Рисование'), (5, 'Музыка')], verbose_name='Направление')), + ('public', models.BooleanField(default=False, verbose_name='Опубликовать')), + ('teachers', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=15, verbose_name='Ссылки на преподов'), default=[], size=None)), + ('image', models.URLField(blank=True, max_length=255, verbose_name='Изображение')), + ('big_image', models.URLField(blank=True, max_length=255, verbose_name='Большое изображение')), + ('big_mobile_image', models.URLField(blank=True, help_text='Большая картинка для мобильной версии', max_length=255, verbose_name='Под мобилку')), + ('hidden', models.BooleanField(default=False, verbose_name='Видно только оплатившим')), + ], + options={ + 'verbose_name': 'Курс', + 'verbose_name_plural': 'Курсы', + }, + ), + migrations.CreateModel( + name='Demand', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='Токен валидатора')), + ('name', models.CharField(max_length=31, unique=True, verbose_name='Название')), + ('checker', models.CharField(choices=[('student', 'student'), ('teacher', 'teacher'), ('support', 'support')], default='teacher', max_length=15, verbose_name='Проверяющий')), + ('min_balls', models.SmallIntegerField(default=50, verbose_name='Проходной бал')), + ], + options={ + 'verbose_name': 'Требования', + 'verbose_name_plural': 'Требования', + }, + ), + migrations.CreateModel( + name='Field', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.CharField(max_length=255, verbose_name='Надпись у поля')), + ('type', models.CharField(choices=[('text', 'text'), ('char', 'char'), ('boolean', 'boolean'), ('file', 'file')], default='char', max_length=20)), + ('multiple', models.BooleanField(default=False)), + ('null', models.BooleanField(default=False)), + ('answers', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, null=True, size=None)), + ('balls', models.SmallIntegerField(default=100, verbose_name='Вознаграждение')), + ('match', models.CharField(blank=True, max_length=255, null=True, verbose_name='Если не нулевое значение, то при точном сопоставлении даёт положителбный ответ')), + ('demand', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Demand')), + ], + options={ + 'verbose_name': 'Поле', + 'verbose_name_plural': 'Поля', + }, + ), + migrations.CreateModel( + name='Lesson', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('token', models.CharField(editable=False, max_length=15, unique=True, verbose_name='Ключ доступа к узлу')), + ('title', models.CharField(max_length=255, verbose_name='Название')), + ('description', models.TextField(blank=True, null=True, verbose_name='Описание')), + ('video', models.TextField(blank=True, null=True, verbose_name='Код видео')), + ('material_tokens', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=15, verbose_name='Материалы урока'), default=[], size=None)), + ('free', models.BooleanField(default=False, verbose_name='Привилегии для узла не будут проверяться')), + ('sort', models.SmallIntegerField(unique=True)), + ], + options={ + 'verbose_name': 'Урок', + 'verbose_name_plural': 'Уроки', + 'ordering': ('sort',), + }, + ), + migrations.CreateModel( + name='LessonDemand', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sort', models.SmallIntegerField(default=1)), + ('demand', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Demand')), + ('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Lesson')), + ], + options={ + 'verbose_name': 'Порядок требований', + 'verbose_name_plural': 'Порядок требований', + 'ordering': ('sort',), + }, + ), + migrations.CreateModel( + name='Topic', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255, verbose_name='Название')), + ('description', models.TextField(blank=True, null=True, verbose_name='Описание')), + ('icon', models.ImageField(blank=True, null=True, upload_to='', verbose_name='Иконка темы')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Course', verbose_name='курс')), + ], + options={ + 'verbose_name': 'Тема', + 'verbose_name_plural': 'Темы', + }, + ), + migrations.AddField( + model_name='lesson', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Topic', verbose_name='Тема'), + ), + migrations.AlterUniqueTogether( + name='lessondemand', + unique_together=set([('lesson', 'demand', 'sort')]), + ), + ] diff --git a/courses/migrations/0002_init_demands.py b/courses/migrations/0002_init_demands.py new file mode 100644 index 0000000..5a18779 --- /dev/null +++ b/courses/migrations/0002_init_demands.py @@ -0,0 +1,42 @@ +from __future__ import unicode_literals + +from courses.models import Demand, Field + +from django.db import migrations + + +def init_demands(*_args, **_kwargs): + d, c = Demand.objects.get_or_create( + min_balls=51, + name="Стандартные требования", + ) + + Field.objects.get_or_create( + demand=d, + label='Комментарий', + type='text', + null=True, + balls=50, + ) + + Field.objects.get_or_create( + demand=d, + label='Приложенные файлы', + type='files', + null=True, + multiple=True, + balls=50, + ) + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('courses', '0001_initial'), + ] + + operations = [ + migrations.RunPython(init_demands) + ] \ No newline at end of file diff --git a/course_service/courses/migrations/__init__.py b/courses/migrations/__init__.py similarity index 100% rename from course_service/courses/migrations/__init__.py rename to courses/migrations/__init__.py diff --git a/course_service/courses/models.py b/courses/models.py similarity index 65% rename from course_service/courses/models.py rename to courses/models.py index d975a79..5dfbafe 100755 --- a/course_service/courses/models.py +++ b/courses/models.py @@ -1,15 +1,13 @@ # -*- coding: utf-8 -*- -import random -import string +import uuid import unidecode from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.template.defaultfilters import slugify +from model_utils import Choices -from course_service.maps.models import CourseRoute, CourseMap -from lms.global_decorators import transaction_decorator from lms.tools import decode_base64, get_real_name COURSE_LEVEL = ( @@ -64,13 +62,6 @@ class CourseManager(models.Manager): return course - def change_route(self, pk, route): - route = CourseRoute.objects.get(id=route) - course = self.get(id=pk) - course.route = route - course.save() - return course - class Course(models.Model): slug = models.SlugField(unique=True, editable=False, max_length=127) @@ -79,7 +70,6 @@ class Course(models.Model): level = models.CharField(verbose_name='Уровень', choices=COURSE_LEVEL, default='B', max_length=3) direction = models.SmallIntegerField(choices=COURSE_DIRECTION, verbose_name='Направление') public = models.BooleanField(verbose_name='Опубликовать', default=False) - route = models.OneToOneField(to=CourseRoute, verbose_name="Порядок прохождения по умолчанию", blank=True, null=True) teachers = ArrayField( models.CharField(max_length=15, blank=True, verbose_name='Ссылки на преподов'), default=[]) image = models.URLField(verbose_name='Изображение', blank=True, max_length=255) @@ -115,25 +105,15 @@ class Topic(models.Model): verbose_name_plural = "Темы" -class Vertex(models.Model): - # TODO переименовать в Lesson +class Lesson(models.Model): token = models.CharField(max_length=15, verbose_name="Ключ доступа к узлу", unique=True, editable=False) topic = models.ForeignKey(to=Topic, verbose_name='Тема') title = models.CharField(verbose_name='Название', max_length=255) description = models.TextField(verbose_name='Описание', blank=True, null=True) video = models.TextField(verbose_name='Код видео', blank=True, null=True) - materials = ArrayField(models.CharField(max_length=15, blank=True, verbose_name='Материалы урока')) - # TODO material_tokens ??? + material_tokens = ArrayField(models.CharField(max_length=15, blank=True, verbose_name='Материалы урока'), default=[]) free = models.BooleanField(default=False, verbose_name='Привилегии для узла не будут проверяться') - - # TODO перейти на список валидаторов - VALID_TYPE = ( - (3, 'Автаматическая валидация'), - (2, 'Полуавтаматическая валидация'), - (1, 'Ручная валидация'), - (0, 'Без валидации'), - ) - valid_type = models.SmallIntegerField(choices=VALID_TYPE, default=0) + sort = models.SmallIntegerField(unique=True) def __str__(self): return self.title @@ -141,3 +121,52 @@ class Vertex(models.Model): class Meta: verbose_name = "Урок" verbose_name_plural = "Уроки" + ordering = ('sort', ) + + +class LessonDemand(models.Model): + demand = models.ForeignKey(to='Demand') + lesson = models.ForeignKey(to=Lesson) + sort = models.SmallIntegerField(default=1) + + class Meta: + verbose_name = "Порядок требований" + verbose_name_plural = "Порядок требований" + ordering = ('sort', ) + unique_together = ('lesson', 'demand', 'sort') + + +class Demand(models.Model): + CHECK_TYPES = Choices('student', 'teacher', 'support',) + token = models.UUIDField(verbose_name="Токен валидатора", default=uuid.uuid4, unique=True, editable=False) + name = models.CharField(max_length=31, verbose_name="Название", unique=True) + checker = models.CharField( + choices=CHECK_TYPES, default=CHECK_TYPES.teacher, max_length=15, verbose_name="Проверяющий",) + min_balls = models.SmallIntegerField(default=50, verbose_name='Проходной бал') + + def __str__(self): + return self.name + + class Meta: + verbose_name = "Требования" + verbose_name_plural = "Требования" + + +class Field(models.Model): + # TODO Нужно прикрутить предварительную валидацию + FIELD_TYPES = Choices('text', 'char', 'boolean', 'file') + demand = models.ForeignKey(to=Demand) + label = models.CharField(max_length=255, verbose_name='Надпись у поля') + type = models.CharField(choices=FIELD_TYPES, default=FIELD_TYPES.char, max_length=20) + multiple = models.BooleanField(default=False) + null = models.BooleanField(default=False) + answers = ArrayField(models.CharField(max_length=255), blank=True, null=True) + balls = models.SmallIntegerField(default=100, verbose_name='Вознаграждение') + match = models.CharField( + max_length=255, + verbose_name='Если не нулевое значение, то при точном сопоставлении даёт положителбный ответ', + blank=True, null=True) + + class Meta: + verbose_name = "Поле" + verbose_name_plural = "Поля" diff --git a/course_service/courses/serializers.py b/courses/serializers.py similarity index 84% rename from course_service/courses/serializers.py rename to courses/serializers.py index fb8cda2..e28683e 100644 --- a/course_service/courses/serializers.py +++ b/courses/serializers.py @@ -1,7 +1,6 @@ from rest_framework import serializers -from course_service.courses.models import Course, Vertex, Topic -from course_service.maps.serializers import CourseRouteSerializer +from courses.models import Course, Lesson, Topic class TopicSerializer(serializers.ModelSerializer): @@ -19,7 +18,7 @@ class TopicSerializer(serializers.ModelSerializer): class MiniVertexSerializer(serializers.ModelSerializer): class Meta: - model = Vertex + model = Lesson fields = ('title', 'free', 'token') @@ -27,7 +26,7 @@ class VertexSerializer(MiniVertexSerializer): valid_type = serializers.SerializerMethodField() class Meta: - model = Vertex + model = Lesson exclude = ('id', 'topic', 'free') @staticmethod @@ -44,7 +43,6 @@ class CourseInitSerializer(serializers.ModelSerializer): class CourseTreeSerializer(serializers.ModelSerializer): tree = serializers.SerializerMethodField() - route = serializers.SerializerMethodField() class Meta: model = Course @@ -54,10 +52,6 @@ class CourseTreeSerializer(serializers.ModelSerializer): def get_tree(self): return [TopicSerializer(i).data for i in self.topic_set.all()] - @staticmethod - def get_route(self): - return CourseRouteSerializer(self.route).data - class CourseDetailSerializer(serializers.ModelSerializer): level = serializers.SerializerMethodField() diff --git a/course_service/courses/tsets.py b/courses/tsets.py similarity index 94% rename from course_service/courses/tsets.py rename to courses/tsets.py index 50be802..22dc4a2 100644 --- a/course_service/courses/tsets.py +++ b/courses/tsets.py @@ -1,8 +1,8 @@ from django.test import TestCase -from course_service.courses.models import Course +from courses.models import Course from lms.tools import EXAMPLE_BASE64 -from course_service.courses.api import InApiTeacher +from courses.api import InApiTeacher class CourseInApiTestCase(TestCase): diff --git a/course_service/courses/urls.py b/courses/urls.py similarity index 81% rename from course_service/courses/urls.py rename to courses/urls.py index efeb1b0..ab6c068 100644 --- a/course_service/courses/urls.py +++ b/courses/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url -from course_service.courses import views as views +from courses import views as views urlpatterns = [ url(r'vertex/(?P.+)/$', views.VertexDetail.as_view()), diff --git a/course_service/courses/views.py b/courses/views.py similarity index 89% rename from course_service/courses/views.py rename to courses/views.py index 75f95b3..d4035f5 100644 --- a/course_service/courses/views.py +++ b/courses/views.py @@ -1,9 +1,9 @@ -from course_service.courses.models import Course, Vertex +from courses.models import Course, Lesson from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView -from course_service.courses.serializers import CourseDetailSerializer, CourseTreeSerializer, VertexSerializer +from courses.serializers import CourseDetailSerializer, CourseTreeSerializer, VertexSerializer class TreeView(APIView): @@ -39,8 +39,8 @@ class VertexDetail(APIView): def get(request, token): try: - vertex = Vertex.objects.get(token=token) - except Vertex.DoesNotExist: + vertex = Lesson.objects.get(token=token) + except Lesson.DoesNotExist: return Response("Vertex doesn't exist", status=404) # if not vertex.check_vertex(request.user): diff --git a/csv/load_bills.py b/csv/load_bills.py index e5c7427..00bdadc 100644 --- a/csv/load_bills.py +++ b/csv/load_bills.py @@ -16,7 +16,7 @@ django.setup() from yandex_money.models import Payment from finance.models import Bill, Invoice from access.models.other import Progress -from course_service.courses.models import Course +from courses.models import Course if __name__ == '__main__': Payment.objects.all().delete() diff --git a/csv/load_comments.py b/csv/load_comments.py index fdb4034..787e91b 100644 --- a/csv/load_comments.py +++ b/csv/load_comments.py @@ -6,7 +6,6 @@ import django import os import sys -from django.contrib.auth import get_user_model from django.db import IntegrityError sys.path.append("../") diff --git a/csv/load_courses.py b/csv/load_courses.py index dc522b1..7ea9e9d 100644 --- a/csv/load_courses.py +++ b/csv/load_courses.py @@ -10,18 +10,12 @@ sys.path.append("../") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.settings") django.setup() -from course_service.courses.api import InApiTeacher +from courses.api import InApiTeacher from django.contrib.auth import get_user_model -from course_service.courses.models import Course, Vertex, Topic -from course_service.maps.models import CourseRoute, CourseMap, PivotVertex, PivotCourseMap +from courses.models import Course, Lesson, Topic from storage.models import File if __name__ == '__main__': - CourseMap.objects.all().delete() - CourseRoute.objects.all().delete() - PivotVertex.objects.all().delete() - PivotCourseMap.objects.all().delete() - Vertex.objects.all().delete() Course.objects.all().delete() with open('./course/course.csv') as user_csv: @@ -82,7 +76,7 @@ if __name__ == '__main__': last_pivot = PivotVertex.objects.filter(map_course=map_obj).last() if model_type == 'tutorial': topic = Topic.objects.get(id=topic_id) - small_vertex = Vertex.objects.create( + small_vertex = Lesson.objects.create( id=pk, video=row.pop('video', None), materials=materials, @@ -100,7 +94,7 @@ if __name__ == '__main__': if model_type == 'task': topic = Topic.objects.get(id=topic_id) - small_vertex = Vertex.objects.create( + small_vertex = Lesson.objects.create( id=pk, materials=materials, topic=topic, diff --git a/csv/load_diploma.py b/csv/load_diploma.py index d0564b8..365567e 100644 --- a/csv/load_diploma.py +++ b/csv/load_diploma.py @@ -6,20 +6,20 @@ sys.path.append("../") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.settings") django.setup() -from achievements.models import Diploma, Achievements +from achievements.models import Diploma, Achievement if __name__ == '__main__': Diploma.objects.all().delete() - Achievements.objects.all().delete() + Achievement.objects.all().delete() with open('./achievement/achievement.csv') as achievements_csv: achievements_reader = csv.DictReader(achievements_csv) for row in achievements_reader: - Achievements.objects.create(**row) + Achievement.objects.create(**row) with open('./achievement/user_achievement.csv') as achievements_csv: achievements_reader = csv.DictReader(achievements_csv) for row in achievements_reader: - a = Achievements.objects.get(id=row['id']) + a = Achievement.objects.get(id=row['id']) a.users.add(get_user_model().objects.get(id=row['student_id'])) with open('./achievement/diploma.csv') as achievements_csv: diff --git a/csv/load_perm.py b/csv/load_perm.py index c53136c..82cc190 100644 --- a/csv/load_perm.py +++ b/csv/load_perm.py @@ -12,8 +12,8 @@ django.setup() from django.contrib.auth.models import Group from django.contrib.auth import get_user_model -from access.models.other import Progress, PivotProgressVertex -from course_service.courses.models import Vertex, Course +from access.models.other import Progress, ProgressLesson +from course_service.courses.models import Lesson, Course from course_service.maps.api import OutApiRoute if __name__ == '__main__': @@ -48,11 +48,11 @@ if __name__ == '__main__': pivots = json.loads(row['success']) for pivot in pivots: try: - pv = PivotProgressVertex.objects.create( + pv = ProgressLesson.objects.create( progress=p, - vertex=Vertex.objects.get(id=pivot['id']).token, + vertex=Lesson.objects.get(id=pivot['id']).token, teacher=get_user_model().objects.get(id=pivot['teacher']), - status=1, + status='wait', ) if pivot['date'] and not pivot['date'] == 'None': @@ -60,7 +60,7 @@ if __name__ == '__main__': pv.status = 2 pv.save() - except Vertex.DoesNotExist: + except Lesson.DoesNotExist: pass g = Group.objects.get(name='students') diff --git a/csv/load_users.py b/csv/load_users.py index c55f73d..9930fa4 100644 --- a/csv/load_users.py +++ b/csv/load_users.py @@ -8,6 +8,7 @@ from django.contrib.auth import get_user_model from access.models.other import Account if __name__ == '__main__': + get_user_model().objects.all().delete() with open('./access/users.csv') as user_csv: user_reader = csv.DictReader(user_csv) for row in user_reader: @@ -18,6 +19,8 @@ if __name__ == '__main__': except IndexError: row['role_list'] = [] + row.pop('id') + get_user_model().objects.get_or_create(**row) except ValueError as e: if str(e) == 'The given email must be set': @@ -30,7 +33,7 @@ if __name__ == '__main__': for row in account_reader: row = dict(row) try: - acc = Account.objects.get(owner=get_user_model().objects.get(id=row['owner'])) + acc = Account.objects.get(owner=get_user_model().objects.get(email=row['owner'])) acc.phone = row['phone'] if row['phone'] and len(row['phone']) < 16 else None acc.city = row['city'] if row['city'] else None acc.photo = row['photo'] diff --git a/finance/migrations/0001_initial.py b/finance/migrations/0001_initial.py index d500563..645b359 100644 --- a/finance/migrations/0001_initial.py +++ b/finance/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:03 +# Generated by Django 1.11.6 on 2018-01-11 10:47 from __future__ import unicode_literals from django.conf import settings @@ -12,8 +12,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('yandex_money', '0002_auto_20171128_1150'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('yandex_money', '0001_initial'), ] operations = [ @@ -37,7 +37,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('status', models.CharField(choices=[('W', 'Ожидание согласия'), ('P', 'На оплате'), ('F', 'Оплачен'), ('C', 'Отклонен')], default='W', max_length=1, verbose_name='Статус')), - ('price', models.IntegerField(editable=False, verbose_name='Сумма')), + ('price', models.IntegerField(blank=True, editable=False, null=True, verbose_name='Сумма')), ('real_price', models.IntegerField(blank=True, editable=False, help_text='Сумма, минус комиссия', null=True, verbose_name='Полученная сумма')), ('method', models.CharField(choices=[('C', 'Наличные'), ('H', 'JustClick'), ('A', 'Альфа-Банк'), ('S', 'SimplePay'), ('Y', 'YandexKassa')], default='Y', max_length=2, verbose_name='Способ оплаты')), ('key', models.CharField(blank=True, editable=False, max_length=255, unique=True, verbose_name='Ключ платежа')), diff --git a/finance/migrations/0002_auto_20171225_1142.py b/finance/migrations/0002_auto_20171225_1142.py deleted file mode 100644 index b5199c6..0000000 --- a/finance/migrations/0002_auto_20171225_1142.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-25 11:42 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('finance', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='invoice', - name='price', - field=models.IntegerField(blank=True, editable=False, null=True, verbose_name='Сумма'), - ), - ] diff --git a/finance/models.py b/finance/models.py index fcdf0e1..33d11f7 100755 --- a/finance/models.py +++ b/finance/models.py @@ -3,7 +3,7 @@ from django.conf import settings from django.db import models from yandex_money.models import Payment -from course_service.courses.models import Course, Vertex +from courses.models import Course, Lesson class Bill(models.Model): diff --git a/library/migrations/0001_initial.py b/library/migrations/0001_initial.py index adf39bd..b0640d4 100644 --- a/library/migrations/0001_initial.py +++ b/library/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:03 +# Generated by Django 1.11.6 on 2018-01-11 10:47 from __future__ import unicode_literals import datetime diff --git a/lms/settings.py b/lms/settings.py index 738ff01..f55139e 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -107,8 +107,7 @@ libs = ( apps = ( 'access', - 'course_service.maps', - 'course_service.courses', + 'courses', 'storage', 'finance', 'library', diff --git a/requirements.txt b/requirements.txt index f8347b1..80ea9e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +django-model-utils celery==4.1.0 Django==1.11.6 django-celery-beat==1.0.1 diff --git a/storage/migrations/0001_initial.py b/storage/migrations/0001_initial.py index 9b6bd6b..299138c 100644 --- a/storage/migrations/0001_initial.py +++ b/storage/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-17 18:03 +# Generated by Django 1.11.6 on 2018-01-11 10:47 from __future__ import unicode_literals from django.db import migrations, models