# Conflicts: # access/admin.py # access/models/__init__.py # access/models/other.py # access/serializers.py # access/urls.py # access/views.py # csv/load_bills.py # csv/load_perm.pyfeature/fix_generate_pass
commit
8d695f1d18
91 changed files with 722307 additions and 34742 deletions
@ -1,8 +1,11 @@ |
||||
from django.contrib import admin |
||||
from access.models.other import Invite, Account, ResetPassword |
||||
from access.models.other import Invite, Account, ResetPassword, UserActivity |
||||
from access.models.progress import ProgressLesson |
||||
from access.models.user import User |
||||
|
||||
admin.site.register(User) |
||||
admin.site.register(Account) |
||||
admin.site.register(Invite) |
||||
admin.site.register(ResetPassword) |
||||
admin.site.register(ProgressLesson) |
||||
admin.site.register(UserActivity) |
||||
|
||||
@ -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), |
||||
), |
||||
] |
||||
@ -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='Дата зачтения задания'), |
||||
), |
||||
] |
||||
@ -0,0 +1,31 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# Generated by Django 1.11.6 on 2018-01-23 10:11 |
||||
from __future__ import unicode_literals |
||||
|
||||
from django.conf import settings |
||||
import django.contrib.postgres.fields |
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('access', '0002_init_group'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='UserActivity', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('ip_list', django.contrib.postgres.fields.ArrayField(base_field=models.GenericIPAddressField(editable=False, verbose_name='Ip адресс'), default=[], size=None)), |
||||
('last_request', models.DateTimeField(auto_now=True, verbose_name='Был в сети')), |
||||
('owner', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
||||
], |
||||
options={ |
||||
'verbose_name': 'Пользовательская активность', |
||||
'verbose_name_plural': 'Пользовательская активность', |
||||
}, |
||||
), |
||||
] |
||||
@ -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), |
||||
), |
||||
] |
||||
@ -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='Ссылки на узлы'), |
||||
), |
||||
] |
||||
@ -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='Токен прохождения'), |
||||
), |
||||
] |
||||
@ -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, |
||||
), |
||||
] |
||||
@ -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='Слаг курса'), |
||||
), |
||||
] |
||||
@ -0,0 +1,88 @@ |
||||
from django.conf import settings |
||||
from django.contrib.postgres.fields import ArrayField |
||||
from django.db import models |
||||
from model_utils import Choices |
||||
from courses.api import CourseProgressApi |
||||
|
||||
|
||||
class Progress(models.Model): |
||||
hidden_lessons = ArrayField( |
||||
models.UUIDField(verbose_name="Токен урока", unique=True, editable=False), |
||||
default=[], |
||||
verbose_name='Список скрытых уроков', |
||||
) |
||||
teacher = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Преподователь по умолчанию", |
||||
related_name='teacher_progress') |
||||
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Студент') |
||||
course_token = models.UUIDField(verbose_name="Токен курса", editable=False) |
||||
|
||||
def progress_status(self, sorted_token_list): |
||||
""" |
||||
Возвращает порядок активной темы и урока или ничего |
||||
|
||||
:return: (topic_sort, lesson_sort) or None |
||||
""" |
||||
|
||||
token_list = [i.lesson_token for i in self.progresslesson_set.filter(status='done')] |
||||
|
||||
x = (1, 1) |
||||
|
||||
for idx_topic, lesson_list in enumerate(sorted_token_list): |
||||
for idx_lesson, lesson_token in enumerate(lesson_list): |
||||
if lesson_token in token_list: |
||||
x = None |
||||
|
||||
elif x is None: |
||||
x = (idx_topic+1, idx_lesson+1) |
||||
|
||||
return x |
||||
|
||||
def __str__(self): |
||||
return '%s' % (self.user.email,) |
||||
|
||||
class Meta: |
||||
verbose_name = 'Прогресс пользователя' |
||||
verbose_name_plural = 'Прогресс пользователя' |
||||
unique_together = (("user", "course_token"),) |
||||
|
||||
|
||||
class ProgressLesson(models.Model): |
||||
progress = models.ForeignKey(to=Progress) |
||||
lesson_token = models.UUIDField(verbose_name="Токен урока", editable=False) |
||||
teacher = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Преподователь",) |
||||
date = models.DateTimeField(verbose_name='Дата зачтения задания', blank=True, null=True) |
||||
STATUSES = Choices('done', 'wait', 'fail') |
||||
status = models.CharField(choices=STATUSES, default=STATUSES.wait, max_length=20) |
||||
comment_tokens = ArrayField(models.UUIDField(verbose_name="Токен комента", editable=False), default=[]) |
||||
|
||||
def __str__(self): |
||||
return self.progress.user.email |
||||
|
||||
class Meta: |
||||
verbose_name = 'Прохождение уроков' |
||||
verbose_name_plural = 'Прохождение урока' |
||||
unique_together = ('progress', 'lesson_token') |
||||
|
||||
|
||||
# class UserLessonAnswer(models.Model): |
||||
# progress_lesson = models.ForeignKey(to=ProgressLesson) |
||||
# date = models.DateTimeField(verbose_name='Дата сдачи', auto_now_add=True) |
||||
# reviewer = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name="Проверяющий",) |
||||
# |
||||
# class Meta: |
||||
# verbose_name = 'Блок ответов пользователя' |
||||
# verbose_name_plural = 'Блоки ответов пользователя' |
||||
# |
||||
# |
||||
# class AnswerItem(models.Model): |
||||
# STATUSES = Choices('done', 'wait', 'fail') |
||||
# |
||||
# lesson_answer = models.ForeignKey(to=UserLessonAnswer) |
||||
# question = models.TextField(verbose_name='Вопрос') # TODO подумать над хранением токена вопроса |
||||
# value = models.TextField(verbose_name='Ответ') |
||||
# comment = models.TextField(verbose_name='Комент', blank=True, null=True) |
||||
# status = models.CharField(choices=STATUSES, default=STATUSES.wait, max_length=20) |
||||
# |
||||
# class Meta: |
||||
# verbose_name = 'Ответ пользователя' |
||||
# verbose_name_plural = 'Ответы пользователя' |
||||
@ -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) |
||||
|
||||
@ -1 +0,0 @@ |
||||
default_app_config = "course_service.courses.apps.CoursesAppConfig" |
||||
@ -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) |
||||
@ -1,21 +0,0 @@ |
||||
from course_service.courses.models import Course |
||||
|
||||
|
||||
class InApiTeacher: |
||||
@staticmethod |
||||
def add_teacher(slug: str, token: str) -> Course: |
||||
course = Course.objects.get(slug=slug) |
||||
course.teachers.append(token) |
||||
course.save() |
||||
return course |
||||
|
||||
@staticmethod |
||||
def delete_teacher(slug: str, token: str) -> None: |
||||
course = Course.objects.get(slug=slug) |
||||
course.teachers.remove(token) |
||||
course.save() |
||||
return None |
||||
|
||||
@staticmethod |
||||
def get_token_list(slug: str) -> list: |
||||
return Course.objects.get(slug=slug).teachers |
||||
@ -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='Порядок прохождения по умолчанию'), |
||||
), |
||||
] |
||||
@ -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), |
||||
), |
||||
] |
||||
@ -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() |
||||
@ -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) |
||||
@ -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) |
||||
@ -1,7 +0,0 @@ |
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class MapsConfig(AppConfig): |
||||
name = "course_service.maps" |
||||
label = 'maps' |
||||
verbose_name = "Отображение курсов" |
||||
@ -1,2 +0,0 @@ |
||||
class MapTypeError(ValueError): |
||||
pass |
||||
@ -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')]), |
||||
), |
||||
] |
||||
@ -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',) |
||||
@ -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()] |
||||
@ -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']) |
||||
@ -1,3 +0,0 @@ |
||||
from django.shortcuts import render |
||||
|
||||
# Create your views here. |
||||
@ -0,0 +1 @@ |
||||
default_app_config = "courses.apps.CoursesAppConfig" |
||||
@ -0,0 +1,6 @@ |
||||
from django.contrib import admin |
||||
|
||||
from courses.models import Course, Topic, Lesson |
||||
admin.site.register(Topic) |
||||
admin.site.register(Lesson) |
||||
admin.site.register(Course) |
||||
@ -0,0 +1,29 @@ |
||||
from courses.models import Course |
||||
|
||||
|
||||
class InApiTeacher: |
||||
@staticmethod |
||||
def add_teacher(slug: str, token: str) -> Course: |
||||
course = Course.objects.get(slug=slug) |
||||
course.teacher_tokens.append(token) |
||||
course.save() |
||||
return course |
||||
|
||||
@staticmethod |
||||
def delete_teacher(slug: str, token: str) -> None: |
||||
course = Course.objects.get(slug=slug) |
||||
course.teacher_tokens.remove(token) |
||||
course.save() |
||||
return None |
||||
|
||||
@staticmethod |
||||
def get_token_list(slug: str) -> list: |
||||
return Course.objects.get(slug=slug).teacher_tokens |
||||
|
||||
|
||||
class CourseProgressApi: |
||||
|
||||
@staticmethod |
||||
def get_topic_lesson(course_token: str): |
||||
course = Course.objects.get(token=course_token) |
||||
return [[lesson.token for lesson in topic.lesson_set.all()] for topic in course.topic_set.all()] |
||||
@ -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): |
||||
@ -1,9 +1,9 @@ |
||||
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<token>.+)/$', views.VertexDetail.as_view()), |
||||
url(r'vertex/(?P<token>.+)/$', views.LessonDetail.as_view()), |
||||
url(r'tree/(?P<slug>.+)/$', views.TreeView.as_view()), |
||||
url(r'^$', views.CourseListView.as_view()), |
||||
] |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
unable to load file from base commit
|
|
unable to load file from base commit
|
File diff suppressed because it is too large
Load Diff
|
unable to load file from base commit
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -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='Сумма'), |
||||
), |
||||
] |
||||
@ -1,5 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
from django.contrib import admin |
||||
|
||||
# Register your models here. |
||||
@ -1,52 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
|
||||
def create_user(email, name): |
||||
""" Создает пользователя в GitLab и возвращает токен пользователя для последующей работы """ |
||||
user_token = '...' |
||||
return user_token |
||||
|
||||
|
||||
def get_user(email, name): |
||||
""" Выбирает пользователя GitLab и возвращает токен пользователя для последующей работы """ |
||||
user_token = '...' |
||||
return user_token |
||||
|
||||
|
||||
def create_repository(user, project_name): |
||||
""" Создает проект (репу) в GitLab и возвращает токен репу """ |
||||
repository_token = '...' |
||||
return repository_token |
||||
|
||||
|
||||
def get_repository(user, project_name): |
||||
""" Возвращает токен репы """ |
||||
repository_token = '...' |
||||
return repository_token |
||||
|
||||
|
||||
def make_user_project_master(user, project): |
||||
""" Делает пользователя мастером в проекте """ |
||||
pass |
||||
|
||||
|
||||
def copy_files_to_repository(base_repository, files_path, target_repository, autor): |
||||
""" Копирует файлы из базовой репы в целевую от имени автора """ |
||||
pass |
||||
|
||||
|
||||
def approve_homework(teacher, base_repository, student, target_repository, files_path): |
||||
""" Принять домашку у студента, копировать новые файлы """ |
||||
# TODO возможно это на уровне LMS надо делать, а с гитлабом вызвать copy_files_to_repository |
||||
pass |
||||
|
||||
|
||||
def make_save_point(teacher, students): |
||||
""" зафиксировать точку в репах с принятыми домашками """ |
||||
pass |
||||
|
||||
|
||||
def get_last_changes(teacher, students, save_point=None): |
||||
""" показать последние изменения в репах учеников относительно save point (последнего, если None) """ |
||||
pass |
||||
|
||||
@ -1,7 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class GitlabServiceConfig(AppConfig): |
||||
name = 'gitlab_service' |
||||
@ -1,106 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
import gitlab |
||||
|
||||
|
||||
class GitlabWrapperException(Exception): |
||||
pass |
||||
|
||||
|
||||
class GitlabWrapperFilesExists(GitlabWrapperException): |
||||
pass |
||||
|
||||
|
||||
class GitlabWrapper: |
||||
API_VERSION = '4' |
||||
|
||||
def __init__(self, url, token, ssl_verify=True): |
||||
self.url = url |
||||
self.token = token |
||||
self.gl = gitlab.Gitlab( |
||||
url=self.url, private_token=self.token, api_version=self.API_VERSION, ssl_verify=ssl_verify) |
||||
|
||||
def get_user(self, email): |
||||
exists_users = self.gl.users.list(search=email) |
||||
if exists_users: |
||||
return exists_users[0] |
||||
|
||||
def create_user(self, email, name): |
||||
username = email[:email.find('@')].replace('.', '_') |
||||
# TODO can contain only letters, digits, '_', '-' and '.'. |
||||
# TODO Cannot start with '-' or end in '.', '.git' or '.atom'." |
||||
exists_users = self.gl.users.list(username=username) |
||||
if len(exists_users): |
||||
username = username + '_{}'.format(len(exists_users) + 1) |
||||
user_data = dict( |
||||
email=email, |
||||
username=username, |
||||
name=name, |
||||
reset_password=True, |
||||
) |
||||
new_user = self.gl.users.create(user_data) |
||||
return new_user |
||||
|
||||
def get_or_create_user(self, email, name): |
||||
user = self.get_user(email) |
||||
if user is None: |
||||
user = self.create_user(email, name) |
||||
return user |
||||
|
||||
def get_user_project(self, user, project_name): |
||||
try: |
||||
exists_project = self.gl.projects.get('{}/{}'.format(user.username, project_name)) |
||||
return exists_project |
||||
except gitlab.GitlabGetError: |
||||
return None |
||||
|
||||
def create_user_project(self, user, project_name): |
||||
user.projects.create(data={'name': project_name}) |
||||
# делаем еще запрос, для получения проекта со всеми аттрибутами |
||||
new_project = self.gl.projects.get('{}/{}'.format(user.username, project_name)) |
||||
return new_project |
||||
|
||||
def get_or_create_user_project(self, user, project_name): |
||||
project = self.get_user_project(user, project_name) |
||||
if project is None: |
||||
project = self.create_user_project(user, project_name) |
||||
return project |
||||
|
||||
def make_user_project_master(self, user, project): |
||||
try: |
||||
project.members.create(data=dict( |
||||
user_id=user.id, |
||||
access_level=40, |
||||
)) |
||||
except gitlab.GitlabCreateError: |
||||
pass |
||||
|
||||
def copy_files_to_repository(self, base_project, files_path, target_project, autor): |
||||
commit_actions = [] |
||||
items = base_project.repository_tree(path=files_path, recursive=True) |
||||
for item in items: |
||||
# item = {'name': '__init__.py', 'path': 'module_01/lesson_01/__init__.py', 'type': 'blob', |
||||
# 'id': '633f866158ac742cf754a2c43edcb07e3a094f3c', 'mode': '100644'} |
||||
if item['type'] == 'blob': |
||||
file_sha = item['id'] |
||||
file_info = base_project.repository_blob(sha=file_sha) |
||||
# file_info = {'content': 'IyAtKi0gY29kaW5nOiB1dGYtOCAtKi0KCg==', 'size': 25, |
||||
# 'sha': '633f866158ac742cf754a2c43edcb07e3a094f3c', 'encoding': 'base64'} |
||||
action = dict( |
||||
action='create', |
||||
file_path=item['path'], |
||||
content=file_info['content'], |
||||
encoding='base64', |
||||
) |
||||
commit_actions.append(action) |
||||
commit_data = { |
||||
'branch': 'master', |
||||
'commit_message': 'Add {}'.format(files_path), |
||||
'actions': commit_actions |
||||
} |
||||
try: |
||||
commit = target_project.commits.create(commit_data, sudo=autor) |
||||
return commit |
||||
except gitlab.GitlabCreateError as exc: |
||||
if exc.response_code == 400: |
||||
raise GitlabWrapperFilesExists() |
||||
raise |
||||
@ -1,21 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
from django.db import models |
||||
from model_utils import Choices |
||||
|
||||
|
||||
class User(models.Model): |
||||
ROLES = Choices( |
||||
('student', 'Студент'), |
||||
('teacher', 'Преподователь'), |
||||
) |
||||
|
||||
token = models.CharField(max_length=256) |
||||
role = models.CharField(max_length=32, choices=ROLES, default=ROLES.student) |
||||
|
||||
|
||||
class Repository(models.Model): |
||||
user = models.ForeignKey(User) |
||||
name = models.CharField(max_length=256) |
||||
|
||||
|
||||
@ -1,4 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from django.test import TestCase |
||||
|
||||
# Create your tests here. |
||||
@ -1,5 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
from django.shortcuts import render |
||||
|
||||
# Create your views here. |
||||
@ -1,43 +1,44 @@ |
||||
amqp==2.2.2 |
||||
Babel==2.5.1 |
||||
billiard==3.5.0.3 |
||||
bson==0.5.0 |
||||
django-model-utils |
||||
celery==4.1.0 |
||||
certifi==2017.11.5 |
||||
chardet==3.0.4 |
||||
Django==1.11.6 |
||||
django-appconf==1.0.2 |
||||
django-celery-beat==1.0.1 |
||||
django-celery-email==2.0.0 |
||||
django-celery-results==1.0.1 |
||||
django-environ==0.4.4 |
||||
django-model-utils==3.0.0 |
||||
django-phonenumber-field==1.3.0 |
||||
django-redis==4.8.0 |
||||
django-redis-sessions==0.6.1 |
||||
django-yandex-money==1.1.2 |
||||
git+https://github.com/yandex-money/yandex-money-kit-django.git |
||||
djangorestframework==3.7.0 |
||||
environ==1.0 |
||||
flower==0.9.2 |
||||
future==0.16.0 |
||||
idna==2.6 |
||||
kombu==4.1.0 |
||||
lxml==4.1.1 |
||||
Naked==0.1.31 |
||||
olefile==0.44 |
||||
phonenumberslite==8.8.8 |
||||
Pillow==4.3.0 |
||||
pkg-resources==0.0.0 |
||||
psycopg2==2.7.3.1 |
||||
python-gitlab==1.1.0 |
||||
pytz==2017.2 |
||||
PyYAML==3.12 |
||||
raven==6.2.1 |
||||
redis==2.10.6 |
||||
requests==2.18.4 |
||||
six==1.11.0 |
||||
tornado==4.5.2 |
||||
Unidecode==0.4.21 |
||||
urllib3==1.22 |
||||
vine==1.1.4 |
||||
yandex-money-sdk==0.1.3 |
||||
|
||||
# amqp==2.2.2 |
||||
# Babel==2.5.1 |
||||
# billiard==3.5.0.3 |
||||
# bson==0.5.0 |
||||
# certifi==2017.11.5 |
||||
# chardet==3.0.4 |
||||
# django-appconf==1.0.2 |
||||
# django-model-utils==3.0.0 |
||||
# environ==1.0 |
||||
# flower==0.9.2 |
||||
# future==0.16.0 |
||||
# idna==2.6 |
||||
# kombu==4.1.0 |
||||
# lxml==4.1.1 |
||||
# Naked==0.1.31 |
||||
# olefile==0.44 |
||||
# phonenumberslite==8.8.8 |
||||
# pkg-resources==0.0.0 |
||||
# python-gitlab==1.1.0 |
||||
# pytz==2017.2 |
||||
# PyYAML==3.12 |
||||
# redis==2.10.6 |
||||
# six==1.11.0 |
||||
# tornado==4.5.2 |
||||
# urllib3==1.22 |
||||
# vine==1.1.4 |
||||
|
||||
Loading…
Reference in new issue