merge from dev

feature/fix_generate_pass
Dmitriy Shesterkin 8 years ago
commit 6ca8139a68
  1. 9
      access/views.py
  2. 33
      courses/migrations/0004_auto_20180222_1756.py
  3. 22
      courses/migrations/0005_auto_20180222_1911.py
  4. 30
      courses/models.py
  5. 25
      courses/serializers.py
  6. 2
      courses/urls.py
  7. 36
      courses/views.py
  8. 18
      finance/views.py
  9. 87
      lms/settings.py
  10. 20
      progress/migrations/0007_progresslesson_last_update.py
  11. 1
      progress/models.py
  12. 2
      progress/serializers.py
  13. 5
      progress/views.py
  14. 7
      storage/models.py

@ -144,8 +144,11 @@ class DetailUserView(APIView):
acc = request.JSON.get('account', None) acc = request.JSON.get('account', None)
if not acc['b_day'] is None: if not acc['b_day'] is None:
b_date = datetime.datetime.strptime(acc['b_day'], '%d.%m.%Y') # TODO вынести форматы в настройки try:
acc['b_day'] = b_date.strftime('%Y-%m-%d') b_day = datetime.datetime.strptime(acc['b_day'], '%d.%m.%Y') # TODO вынести форматы в настройки
except ValueError:
b_day = datetime.datetime.strptime(acc['b_day'], '%d-%m-%Y')
acc['b_day'] = b_day.strftime('%Y-%m-%d')
acc['gender'] = 0 if acc['gender'] == "undefined" else 1 if acc['gender'] == "male" else 2 acc['gender'] = 0 if acc['gender'] == "undefined" else 1 if acc['gender'] == "male" else 2
@ -253,7 +256,7 @@ class LoginView(APIView):
try: try:
auth.login(request, user) auth.login(request, user)
except AttributeError: except AttributeError:
return Response("Неверный пароль", status=404) return Response("Неверный пароль", status=403)
serialized_user = UserSelfSerializer(user).data serialized_user = UserSelfSerializer(user).data
serialized_user['is_i'] = True serialized_user['is_i'] = True

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-02-22 17:56
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('courses', '0003_auto_20180219_1323'),
]
operations = [
migrations.AlterField(
model_name='lesson',
name='sort',
field=models.SmallIntegerField(verbose_name='Поле сортировки'),
),
migrations.AlterField(
model_name='topic',
name='sort',
field=models.SmallIntegerField(verbose_name='Поле сортировки'),
),
migrations.AlterUniqueTogether(
name='lesson',
unique_together=set([('sort', 'topic')]),
),
migrations.AlterUniqueTogether(
name='topic',
unique_together=set([('sort', 'course')]),
),
]

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-02-22 19:11
from __future__ import unicode_literals
import django.contrib.postgres.fields
from django.db import migrations, models
import lms.tools
class Migration(migrations.Migration):
dependencies = [
('courses', '0004_auto_20180222_1756'),
]
operations = [
migrations.AlterField(
model_name='lesson',
name='material_tokens',
field=django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(editable=False, verbose_name='Токен материала'), blank=True, default=lms.tools.get_empty_list, size=None, verbose_name='Материалы курса'),
),
]

@ -37,9 +37,10 @@ class Lesson(models.Model):
models.UUIDField(verbose_name="Токен материала", editable=False), models.UUIDField(verbose_name="Токен материала", editable=False),
default=get_empty_list, default=get_empty_list,
verbose_name='Материалы курса', verbose_name='Материалы курса',
blank=True
) )
free = models.BooleanField(default=False, verbose_name='Привилегии для узла не будут проверяться') free = models.BooleanField(default=False, verbose_name='Привилегии для узла не будут проверяться')
sort = models.SmallIntegerField(unique=True) sort = models.SmallIntegerField(verbose_name='Поле сортировки')
is_hm = models.BooleanField(default=False) #TODO костыли is_hm = models.BooleanField(default=False) #TODO костыли
old_id = models.IntegerField(null=True, blank=True) old_id = models.IntegerField(null=True, blank=True)
@ -51,6 +52,7 @@ class Lesson(models.Model):
verbose_name = "Урок" verbose_name = "Урок"
verbose_name_plural = "Уроки" verbose_name_plural = "Уроки"
ordering = ('sort', ) ordering = ('sort', )
unique_together = ('sort', 'topic')
class Topic(models.Model): class Topic(models.Model):
@ -58,30 +60,42 @@ class Topic(models.Model):
title = models.CharField(verbose_name='Название', max_length=255) title = models.CharField(verbose_name='Название', max_length=255)
description = models.TextField(verbose_name='Описание', blank=True, null=True) description = models.TextField(verbose_name='Описание', blank=True, null=True)
icon = models.ImageField(verbose_name='Иконка темы', null=True, blank=True) icon = models.ImageField(verbose_name='Иконка темы', null=True, blank=True)
sort = models.SmallIntegerField(unique=True) sort = models.SmallIntegerField(verbose_name='Поле сортировки')
def __str__(self):
return self.title
class Meta: class Meta:
verbose_name = "Тема" verbose_name = "Тема"
verbose_name_plural = "Темы" verbose_name_plural = "Темы"
ordering = ('sort',) ordering = ('sort',)
unique_together = ('sort', 'course')
class CourseManager(models.Manager): class CourseManager(models.Manager):
def update_or_create_course(self, image=None, big_image=None, id=0, def update_or_create_course(self, image=None, big_image=None, statistic=None,
big_mobile_image=None, slug=None, big_mobile_image=None, slug=None, teachers=None,
level=None, direction=None, **kwargs): level=None, direction=None, **kwargs):
slug = slug if slug else slugify(unidecode.unidecode(kwargs['title'])) slug = slug if slug else slugify(unidecode.unidecode(kwargs['title']))
kwargs['teacher_tokens'] = teachers
if image: if image:
kwargs['image'] = decode_base64(image, 'course/image%s.png' % slug) path = 'course/image%s.png' % slug
decode_base64(image, path)
kwargs['image'] = path
if big_image: if big_image:
kwargs['big_image'] = decode_base64(big_image, 'course/big_image%s.png' % slug) path = 'course/big_image%s.png' % slug
decode_base64(image, path)
kwargs['big_image'] = path
if big_mobile_image: if big_mobile_image:
kwargs['big_mobile_image'] = decode_base64(big_mobile_image, 'course/big_mobile_image%s.png' % slug) path = 'course/big_mobile_image%s.png' % slug
decode_base64(image, path)
kwargs['big_mobile_image'] = path
if level: if level:
kwargs['level'] = get_real_name(COURSE_LEVEL, level) kwargs['level'] = get_real_name(COURSE_LEVEL, level)
@ -90,7 +104,7 @@ class CourseManager(models.Manager):
kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction) kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction)
try: try:
course = self.get(id=id) course = self.get(slug=slug)
for i in kwargs: for i in kwargs:
if kwargs[i]: if kwargs[i]:
setattr(course, i, kwargs[i]) setattr(course, i, kwargs[i])

@ -29,6 +29,31 @@ class LessonSerializer(MiniLessonSerializer):
exclude = ('id', 'topic', 'key') exclude = ('id', 'topic', 'key')
class TeacherLessonSerializer(MiniLessonSerializer):
topic_sort = serializers.SerializerMethodField()
topic_title = serializers.SerializerMethodField()
course_title = serializers.SerializerMethodField()
class Meta:
model = Lesson
fields = ('topic_sort', 'description', 'title', 'course_title', 'token', 'topic_title')
@staticmethod
def get_topic_sort(self):
for topic_idx, topic in enumerate(self.topic.course.topic_set.all()):
if topic == self.topic:
return topic_idx + 1
return None
@staticmethod
def get_topic_title(self):
return self.topic.title
@staticmethod
def get_course_title(self):
return self.topic.course.title
class CourseInitSerializer(serializers.ModelSerializer): class CourseInitSerializer(serializers.ModelSerializer):
class Meta: class Meta:

@ -4,6 +4,8 @@ from courses import views as views
urlpatterns = [ urlpatterns = [
url(r'vertex/(?P<token>.+)/$', views.LessonDetail.as_view()), url(r'vertex/(?P<token>.+)/$', views.LessonDetail.as_view()),
url(r'lesson/teacher/(?P<token>.+)/$', views.LessonInfoView.as_view()),
url(r'tree/(?P<slug>.+)/$', views.TreeView.as_view()), url(r'tree/(?P<slug>.+)/$', views.TreeView.as_view()),
url(r'detail/(?P<slug>.+)/$', views.CourseDetailView.as_view()),
url(r'^$', views.CourseListView.as_view()), url(r'^$', views.CourseListView.as_view()),
] ]

@ -2,8 +2,9 @@ from courses.models import Course, Lesson
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from django.contrib.auth import get_user_model
from courses.serializers import CourseDetailSerializer, CourseTreeSerializer, LessonSerializer from courses.serializers import CourseDetailSerializer, CourseTreeSerializer, LessonSerializer, TeacherLessonSerializer
from progress.models import ProgressLesson from progress.models import ProgressLesson
@ -43,6 +44,9 @@ class CourseListView(APIView):
location: form location: form
... ...
""" """
# TODO: Костыль
teachers_emails = request.JSON.get('teachers', [])
request.JSON['teachers'] = [get_user_model().objects.get(email=i).out_key for i in teachers_emails]
course = Course.objects.update_or_create_course(**request.JSON.dict()) course = Course.objects.update_or_create_course(**request.JSON.dict())
return Response(CourseDetailSerializer(course).data, status=self.status_code) return Response(CourseDetailSerializer(course).data, status=self.status_code)
@ -55,6 +59,36 @@ class CourseListView(APIView):
return Response(res, self.status_code) return Response(res, self.status_code)
class CourseDetailView(APIView):
renderer_classes = (JSONRenderer,)
status_code = 200
@staticmethod
def delete(request, slug):
try:
Course.objects.get(slug=slug).delete()
except Course.DoesNotExist:
return Response("Курса не существует", status=404)
return Response(status=204)
def get(self, request, slug):
return Response(CourseDetailSerializer(Course.objects.get(slug=slug)).data, self.status_code)
class LessonInfoView(APIView):
renderer_classes = (JSONRenderer,)
status_code = 200
def get(self, request, token):
try:
lesson = Lesson.objects.get(token=token)
except Lesson.DoesNotExist:
return Response('Урок не найден', status=404)
if request.user.is_authenticated and request.user.out_key in lesson.topic.course.teacher_tokens:
return Response(TeacherLessonSerializer(lesson).data, self.status_code)
return Response("Пользователь не является преподователем по курсу", status=403)
class LessonDetail(APIView): class LessonDetail(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)

@ -37,13 +37,16 @@ class BillListView(APIView):
or request.user.is_superuser): or request.user.is_superuser):
bill = request.JSON.get('bill') bill = request.JSON.get('bill')
children = request.JSON.get('children', []) children = request.JSON.get('children', [])
bill_kwarg = dict()
if bill: if bill:
bill['user'] = get_user_model().objects.get(email=bill['user']) bill_kwarg['user'] = get_user_model().objects.get(email=bill['user'])
bill['opener'] = get_user_model().objects.get(email=bill['opener']) bill_kwarg['opener'] = get_user_model().objects.get(email=bill['opener'])
bill.pop('invoices', None) bill_kwarg['description'] = bill['description']
bill_kwarg['comment'] = bill['comment']
bill_kwarg['course_token'] = bill['course_token']
bill_obj, is_create = Bill.objects.update_or_create(**bill) bill_obj, is_create = Bill.objects.update_or_create(**bill_kwarg)
invoices = bill_obj.invoice_set.all() invoices = bill_obj.invoice_set.all()
for i in children: for i in children:
@ -173,15 +176,16 @@ def get_invoices(request):
invoices = invoices.filter(date__gte=date_from) if date_from else invoices invoices = invoices.filter(date__gte=date_from) if date_from else invoices
response = HttpResponse(content_type='text/csv') response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s"' % file_name response['Content-Disposition'] = 'attachment; filename="%s.csv"' % file_name
writer = csv.writer(response) writer = csv.writer(response)
writer.writerow(['date', 'student_email', 'full_name', 'course', 'price', 'real_price', 'key']) writer.writerow(['date', 'time', 'student_email', 'full_name', 'course', 'price', 'real_price', 'key'])
for i in invoices.order_by('-date'): for i in invoices.order_by('-date'):
course_api = CourseParamsApi(i.bill.course_token) course_api = CourseParamsApi(i.bill.course_token)
writer.writerow([ writer.writerow([
i.date, i.date.date(),
i.date.time(),
i.bill.user.email, i.bill.user.email,
i.bill.user.get_full_name(), i.bill.user.get_full_name(),
course_api.get_slug_and_title()['title'], course_api.get_slug_and_title()['title'],

@ -68,6 +68,8 @@ DATABASES = {
SESSION_ENGINE = 'redis_sessions.session' SESSION_ENGINE = 'redis_sessions.session'
CELERY_EMAIL_CHUNK_SIZE = 1 CELERY_EMAIL_CHUNK_SIZE = 1
DATA_UPLOAD_MAX_MEMORY_SIZE = 12621440
CACHES = { CACHES = {
'default': env.cache(), 'default': env.cache(),
} }
@ -187,49 +189,48 @@ RAVEN_CONFIG = {
'dsn': 'http://1a09557dbd144e52af4b14bea569c114:fbb5dfaa39e64f02a1b4cc7ac665d7d7@sentry.skillbox.ru/7' 'dsn': 'http://1a09557dbd144e52af4b14bea569c114:fbb5dfaa39e64f02a1b4cc7ac665d7d7@sentry.skillbox.ru/7'
} }
# LOGGING = { LOGGING = {
# 'version': 1, 'version': 1,
# 'disable_existing_loggers': True, 'disable_existing_loggers': True,
# 'root': { 'root': {
# 'level': 'WARNING', 'level': 'WARNING',
# 'handlers': ['sentry'], 'handlers': ['sentry'],
# }, },
# 'formatters': { 'formatters': {
# 'verbose': { 'verbose': {
# 'format': '%(levelname)s %(asctime)s %(module)s ' 'format': '%(levelname)s %(asctime)s %(module)s '
# '%(process)d %(thread)d %(message)s' '%(process)d %(thread)d %(message)s'
# }, },
# }, },
# 'handlers': { 'handlers': {
# 'sentry': { 'sentry': {
# 'level': 'ERROR', # To capture more than ERROR, change to WARNING, INFO, etc. 'level': 'WARNING', # To capture more than ERROR, change to WARNING, INFO, etc.
# 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
# 'tags': {'custom-tag': 'x'}, 'tags': {'custom-tag': 'x'},
# }, },
# 'console': { 'yandex_money': {
# 'level': 'DEBUG', 'level': 'DEBUG',
# 'class': 'logging.StreamHandler', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
# 'formatter': 'verbose' 'tags': {'custom-tag': 'yandex'},
# } },
# }, 'console': {
# 'loggers': { 'level': 'DEBUG',
# 'django.db.backends': { 'class': 'logging.StreamHandler',
# 'level': 'ERROR', 'formatter': 'verbose'
# 'handlers': ['console'], }
# 'propagate': False, },
# }, 'loggers': {
# 'raven': { '': {
# 'level': 'DEBUG', 'level': 'WARNING',
# 'handlers': ['console'], 'handlers': ['sentry'],
# 'propagate': False, },
# }, 'yandex_money': {
# 'sentry.errors': { 'handlers': ['yandex_money'],
# 'level': 'DEBUG', 'level': 'DEBUG',
# 'handlers': ['console'], 'propagate': False
# 'propagate': False, },
# }, },
# }, }
# }
SWAGGER_SETTINGS = { SWAGGER_SETTINGS = {
'USE_SESSION_AUTH': True, 'USE_SESSION_AUTH': True,

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-02-25 18:28
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('progress', '0006_auto_20180219_1323'),
]
operations = [
migrations.AddField(
model_name='progresslesson',
name='last_update',
field=models.DateTimeField(auto_now=True, verbose_name='Дата последнего изменения'),
),
]

@ -57,6 +57,7 @@ class ProgressLesson(models.Model):
finish_date = models.DateTimeField(verbose_name='Дата зачтения задания', blank=True, null=True) finish_date = models.DateTimeField(verbose_name='Дата зачтения задания', blank=True, null=True)
start_date = models.DateTimeField(verbose_name='Дата начала прохождения задания', auto_now_add=True) start_date = models.DateTimeField(verbose_name='Дата начала прохождения задания', auto_now_add=True)
status = models.CharField(choices=STATUSES, default=STATUSES.start, max_length=20) status = models.CharField(choices=STATUSES, default=STATUSES.start, max_length=20)
last_update = models.DateTimeField(verbose_name='Дата последнего изменения', auto_now=True)
comment_tokens = ArrayField(models.UUIDField(verbose_name="Токен комента", editable=False), default=get_empty_list) comment_tokens = ArrayField(models.UUIDField(verbose_name="Токен комента", editable=False), default=get_empty_list)
def __str__(self): def __str__(self):

@ -39,7 +39,7 @@ class ProgressLessonSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ProgressLesson model = ProgressLesson
exclude = ('id', 'progress', 'checker') exclude = ('progress', 'checker')
@staticmethod @staticmethod
def get_teacher(self): def get_teacher(self):

@ -23,6 +23,8 @@ class StudentWorkView(APIView):
@staticmethod @staticmethod
def get(request, teacher_token): def get(request, teacher_token):
client_status = request.GET.get('status', 'in_progress') client_status = request.GET.get('status', 'in_progress')
client_max_body = 50
last_id = request.GET.get('last_id', 0)
server_status = Q(status='fail') if \ server_status = Q(status='fail') if \
client_status == 'not_done' else Q(status='wait') if client_status == 'in_progress' else Q(status='done') client_status == 'not_done' else Q(status='wait') if client_status == 'in_progress' else Q(status='done')
if request.user.is_authenticated() and request.user.groups.filter(name__in=['teachers', 'admin']).exists(): if request.user.is_authenticated() and request.user.groups.filter(name__in=['teachers', 'admin']).exists():
@ -31,7 +33,8 @@ class StudentWorkView(APIView):
~Q(progress__user__out_key=teacher_token), ~Q(progress__user__out_key=teacher_token),
server_status, server_status,
checker__out_key=teacher_token, checker__out_key=teacher_token,
) id__gt=last_id
)[:client_max_body]
return Response([ProgressLessonSerializer(i).data for i in progress_lessons], status=200) return Response([ProgressLessonSerializer(i).data for i in progress_lessons], status=200)
except ValidationError: except ValidationError:
return Response("Bad request", status=400) return Response("Bad request", status=400)

@ -10,9 +10,10 @@ class FileManager(models.Manager):
def upload_as_base64(self, file_base64): def upload_as_base64(self, file_base64):
if "data:" in file_base64: if "data:" in file_base64:
my_str = file_base64[file_base64.index("base64,") + 7:] my_str = file_base64[file_base64.index(";base64,")+8:]
ext = my_str.split('/')[-1] content_type = file_base64[:file_base64.index(";base64,")].split('/')[1]
file = self.create(original=ContentFile(base64.b64decode(my_str), name='time.' + ext)) file_source = ContentFile(base64.b64decode(my_str), name='time.' + content_type)
file = self.create(original=file_source)
return file return file
raise ValueError() raise ValueError()

Loading…
Cancel
Save