Giter Site home page Giter Site logo

tough-dev-school / education-backend Goto Github PK

View Code? Open in Web Editor NEW
341.0 7.0 63.0 4.41 MB

Django-based backend for our learning management system

Home Page: https://tough-dev.school

License: MIT License

Python 99.33% Dockerfile 0.31% CSS 0.07% JavaScript 0.18% Makefile 0.11%
django python django-rest-framework django-application real-world

education-backend's People

Contributors

abdujabbar avatar da-maltsev avatar dependabot-preview[bot] avatar dependabot[bot] avatar drakulavich avatar f213 avatar grepto avatar hnthh avatar igoose1 avatar inerv avatar juzexe avatar metheoryt avatar nkiryanov avatar nvo87 avatar paulinenik avatar renovate[bot] avatar sobolevn avatar taranovskiy avatar tvojamamalama avatar vlkpn avatar withshubh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

education-backend's Issues

FieldError: Cannot resolve keyword 'auhor' into field. Choices are: answeraccesslogentry, answercrosscheck, a...

Sentry Issue: EDUCATION-BACKEND-3D

FieldError: Cannot resolve keyword 'auhor' into field. Choices are: answeraccesslogentry, answercrosscheck, author, author_id, children, created, crosscheck_count, do_not_crosscheck, id, modified, parent, parent_id, question, question_id, slug, text
(10 additional frame(s) were not displayed)
...
  File "django/contrib/admin/views/main.py", line 99, in __init__
    self.queryset = self.get_queryset(request)
  File "django/contrib/admin/views/main.py", line 480, in get_queryset
    qs = qs.order_by(*ordering)
  File "django/db/models/query.py", line 1134, in order_by
    obj.query.add_ordering(*field_names)
  File "django/db/models/sql/query.py", line 1919, in add_ordering
    self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
  File "django/db/models/sql/query.py", line 1481, in names_to_path
    raise FieldError("Cannot resolve keyword '%s' into field. "

FieldError: Unknown field(s) (view, checked, author) specified for AnswerCrossCheck. Check fields/fieldsets/e...

Sentry Issue: EDUCATION-BACKEND-38

FieldError: Unknown field(s) (view, checked, author) specified for AnswerCrossCheck
  File "django/contrib/admin/options.py", line 709, in get_form
    return modelform_factory(self.model, **defaults)
  File "django/forms/models.py", line 555, in modelform_factory
    return type(form)(class_name, (form,), form_class_attrs)
  File "django/forms/models.py", line 268, in __new__
    raise FieldError(message)

FieldError: Unknown field(s) (view, checked, author) specified for AnswerCrossCheck. Check fields/fieldsets/exclude attributes of class AnswerCrossCheckAdmin.
(7 additional frame(s) were not displayed)
...
  File "django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "django/contrib/admin/options.py", line 1534, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "django/contrib/admin/options.py", line 1568, in _changeform_view
    ModelForm = self.get_form(
  File "django/contrib/admin/options.py", line 711, in get_form
    raise FieldError(

Кнопка в админке «выполнить ещё раз» для заказа

Бывает, что нужно отправить пользователю ещё один комплект записей или что мы там продаём. При этом отменять оплату и добавлять её заново не хочется — собьётся бухглатерия.

Надо в списке массовых операций добавить кнопку «отправить ещё раз», которая будет делать это только для оплаченных заказов.

PermissionDenied: У вас недостаточно прав для выполнения данного действия.

Sentry Issue: EDUCATION-BACKEND-31

PermissionDenied: У вас недостаточно прав для выполнения данного действия.
  File "rest_framework/views.py", line 497, in dispatch
    self.initial(request, *args, **kwargs)
  File "rest_framework/views.py", line 415, in initial
    self.check_permissions(request)
  File "rest_framework/views.py", line 333, in check_permissions
    self.permission_denied(
  File "rest_framework/views.py", line 175, in permission_denied
    raise exceptions.PermissionDenied(detail=message, code=code)

Письмо через 3 дня после покупки записи

Что

У нас сейчас есть триггер-догонялка — он пишет письмо от моего имени с вопросом о том, что помешало купить.

Я хочу похожий триггер, но чтобы он случался через 3 дня после покупки каждой записи (не курса), и спрашивал, типа «как вам»? Письмо я напишу сам, с тебя — только триггер.

Чтобы что

  • Чтобы собирать обратную связь
  • Чтобы люди не забывали скачивать запись

Удовлетворить любопытство [pls :)]

Do not use signals for business logic. Signals are good only for notification purposes.
(C) README.md

Не совсем issue, а скорее вопрос - что не так с Signals? Или речь идёт о какой-то специфичной "business logic"?

ChunkLoadError: Loading chunk answers~questions failed.

Sentry Issue: EDUCATION-FRONTEND-8

ChunkLoadError: Loading chunk answers~questions failed.
(missing: https://education.borshev.com/lms/js/answers~questions.8811eed8.js)
  at reject (./src/router/index.js:18:22)
  at map ([native code])
  at map ([native code])
  at ensureURL (./src/router/index.js:64:3)
  at Promise ([native code])
...
(24 additional frame(s) were not displayed)

Уведомления о проверке домашки

  • Подсвечивать на фронте ответ, по которому перешли
  • Обработать ситуацию, когда пользователь перешёл по ссылке на ответ, которого нет

Мёрджить пользователей с имейлом в разном кейсе

Технически, у нас [email protected] и [email protected] — это два разных имейла. Если в базе есть оба имейла, и юзеры логинятся по имейлу, то они могут нарваться на 403, потому что курс оформлен на имейл с большой буквы, а юзер логинится по имейлу с маленькой буквы.

Нужно сделать команду, которая мёрджит таких пользователей (не теряя их покупки) и докрутить UserCreator, чтобы он не создавал таких дублей.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Other Branches

These updates are pending. To force PRs open, click the checkbox below.

  • chore(deps): update dependency djangorestframework-stubs to v3.15.0
  • fix(deps): update dependency django-filter to v24

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

poetry
pyproject.toml
  • babel ^2.12.1
  • bcrypt ^4.0.1
  • bleach ^6.0.0
  • celery 5.4.0
  • cmarkgfm ^2024.0.0
  • dj-rest-auth ^6.0.0
  • django ^4.2.5
  • django-anymail ^10.1
  • django-axes ^6.1.1
  • django-behaviors ^0.5.1
  • django-cachalot ^2.6.1
  • django-cors-headers ^4.2.0
  • django-debug-toolbar ^4.2.0
  • django-environ ^0.11.2
  • django-filter ^23.2
  • django-healthchecks ^1.5.0
  • django-ipware ^7.0.0
  • django-prettyjson ^0.4.1
  • django-split-settings ^1.2.0
  • django-storages ^1.14
  • django-tree-queries ^0.19.0
  • djangorestframework ^3.14.0
  • drf-jwt ^1.19.2
  • drf-recaptcha ^3.0.0
  • drf-spectacular ^0.27.0
  • emoji ^2.8.0
  • httpx ^0.27.0
  • pillow ^10.1.0
  • psycopg2-binary ^2.9.7
  • python ~3.11
  • python-benedict ^0.33.0
  • redis ^5.0.0
  • retry ^0.9.2
  • sentry-sdk ^2.0.0
  • shortuuid ^1.0.11
  • simplejson ^3.19.1
  • stripe ^6.5.0
  • whitenoise ^6.5.0
  • django-stubs ^4.2.6
  • djangorestframework-stubs ^3.14.4
  • freezegun ^1.2.2
  • ipython ^8.15.0
  • jedi ^0.19.0
  • mixer ^7.2.2
  • mypy ^1.6.1
  • pytest-cov ^5.0.0
  • pytest-deadfixtures ^2.2.1
  • pytest-django ^4.5.2
  • pytest-env ^1.0.1
  • pytest-freezegun ^0.4.2
  • pytest-mock ^3.11.1
  • pytest-randomly ^3.15.0
  • pytest-repeat ^0.9.1
  • pytest-xdist ^3.3.1
  • requests-mock ^1.11.0
  • respx ^0.21.0
  • rope ^1.10.0
  • ruff ^0.4.0
  • toml-sort ^0.23.1
  • types-freezegun ^1.1.10

  • Check this box to trigger a request for Renovate to run again on this repository

Написать тесты на v2/markdownx/markdownify

Наш фронт ходит во внутреннее API django-markdownx. Надо написать тесты на то, что это API работает: они понадобятся в случае, если мы когда-нибудь решим отказаться от django-markdownx. Если мы выпилим его без тестов, то узнаем о падении только с фронта, а если выпилим и у нас упадут наши тесты — сразу станет понятно, что этот эндпоинт нужен на фронте.

AttributeError: Nor <class 'django.db.models.manager.DefaultManagerFromAnswerQuerySet'>, nor AnswerQuerySet or An...

Sentry Issue: EDUCATION-BACKEND-34

AttributeError: Nor <class 'django.db.models.manager.DefaultManagerFromAnswerQuerySet'>, nor AnswerQuerySet or AnswerQuerySet.Q does not have `ancestors` defined.
(13 additional frame(s) were not displayed)
...
  File "django/forms/forms.py", line 376, in full_clean
    self._post_clean()
  File "django/forms/models.py", line 405, in _post_clean
    self.instance.full_clean(exclude=exclude, validate_unique=False)
  File "django/db/models/base.py", line 1216, in full_clean
    self.clean()
  File "tree_queries/models.py", line 52, in clean
    self.__class__._default_manager.ancestors(
  File "app/models.py", line 74, in __getattr__
    raise AttributeError(f'Nor {self.__class__}, nor {self._queryset_class.__name__} or {self._queryset_class.__name__}.Q does not have `{name}` defined.')

TypeError: ship() got an unexpected keyword argument 'order'

Sentry Issue: EDUCATION-BACKEND-2S

TypeError: ship() got an unexpected keyword argument 'order'
(10 additional frame(s) were not displayed)
...
  File "app/admin/decorators.py", line 9, in decorated
    return fn(*args, **kwargs)
  File "orders/admin/orders.py", line 123, in ship_again_if_paid
    order.ship(silent=True)
  File "orders/models/order.py", line 99, in ship
    Pigwidgeon(self, silent=silent)()
  File "orders/services/order_shipper.py", line 15, in __call__
    if self.ship():
  File "orders/services/order_shipper.py", line 28, in ship
    self.order.item.ship(to=self.order.user, order=self.order)

AnymailRecipientsRefused: All message recipients were rejected or invalid

Sentry Issue: EDUCATION-BACKEND-21

AnymailRecipientsRefused: All message recipients were rejected or invalid
Sending a message to <REDACTED> from Fedor Borshev <[email protected]>
(3 additional frame(s) were not displayed)
...
  File "django/core/mail/message.py", line 284, in send
    return self.get_connection(fail_silently).send_messages([self])
  File "anymail/backends/base.py", line 94, in send_messages
    sent = self._send(message)
  File "anymail/backends/base_requests.py", line 58, in _send
    return super()._send(message)
  File "anymail/backends/base.py", line 131, in _send
    self.raise_for_recipient_status(message.anymail_status, response, payload, message)
  File "anymail/backends/base.py", line 193, in raise_for_recipient_status
    raise AnymailRecipientsRefused(email_message=message, payload=payload, response=response,

Промо-коды: обрезать пробелы и другую фигню

Когда пользователи копируют промо-код (скажем, с рекламы), они могут случайно скопировать пробел в конце или в начале. Бекенд в таком случае ничего не поймёт — всё будет выглядеть так, как будто мы дали неправильный промо-код, и пользователь пойдёт и купит какой-нибудь другой курс.

ValidationError: {'parent': [ErrorDetail(string='Это поле не может быть пустым.', code='null')]}

Sentry Issue: EDUCATION-BACKEND-33

ValidationError: {'parent': [ErrorDetail(string='Это поле не может быть пустым.', code='null')]}
  File "rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "rest_framework/generics.py", line 242, in post
    return self.create(request, *args, **kwargs)
  File "homework/api/views.py", line 60, in create
    return super().create(request, *args, **kwargs)
  File "rest_framework/mixins.py", line 18, in create
    serializer.is_valid(raise_exception=True)
  File "rest_framework/serializers.py", line 228, in is_valid
    raise ValidationError(self.errors)

Сделать кнопку в админке «зайти от имени юзера»

В #301 мы сделали эндпоинт, который позволяет заходить суперюзеру от имени другого юзера. На фронте соответствующая страница, которая обслуживает этот адрес находится в '/auth/as/:id' (см. роуты).

Нужно через get_absolute_url() сделать, чтобы на странице юзера в админке открывался фронт от имени нового юзера, т.е. вышеупомянутая страничка фронта.

Массовые операции над промокодами

Всякий раз перед запуском нового курса приходится обходить старые промокоды и отключать их. Хочу массовую операцию, в админке. чтобы я просто выбрал их все и отключил.

Отдельные списки рассылки для курсов и записей

Что

У нас есть механизм, который подписывает всех новых юзеров на список рассылки. Таким образом у меня копится база подписчиков:
Screenshot 2019-12-23 at 15 27 12

Хочу, чтобы параллельно с этим, если процесс покупки всё-таки завершён, то юзеры подписывались ещё на отдельный специфичный контакт-лист для каждого курса. Скажем, если юзер записался на вебинар про tdd, или купил его запись, то он автоматически подписывался ещё на список рассылки для этого вебинара.

То есть в админке у курса делаем поле mailjet_list_id, и если оно есть — в процессе отправки покупки подписываем на него юзера.

За доступом к мейлджету, если надо обращайся в почту.

Чтобы что

Мне иногда нужны списки рассылки, чтобы, скажем, напомнить людям проверить папку «спам», или для какой-нибудь маркетинговой активности. Не хочется выгружать это всё вручную.

Переписать интеграцию с телеграмом

Сейчас используем какой-то мутный сервис для отправки сообщения в телегу. Так вышло потому, что когда мы запускались, API телеграма было раскомнадзорено.

Надо отказаться от использования этого сервиса. Заодно, сделать возможность указывать отдельные каналы уведомлений о новых заказах для разных курсов.

Перевести связь Answer со студентом на модель Study

У нас появилась модель Study, которая фиксирует факт обучения студента на определённом курсе.

Нужно сделать так, чтобы связь ответа на домашку со студентом была через эту модель.

В таком случае курс нужно будет определять в момент создания ответа, а метод Answer.get_purchased_course() потеряет свой смысл

Админка: смигрировать с @field на @display

У нас есть декоратор @field, который используется для короткого объявления readonly полей в админке, к примеру так.

В джангу 3.2 завезли свой такой декторатор, который называется @display. Надо смигрировать на него.

Более явный деплой на хероку

Сейчас деплой на хероку происходит средствами самого хероку, когда закончились проверки CI в мастер. Надо перевести его в CircleCI, чтобы процесс деплоя был частью общего единого пайплайна. Для этого отлично подойдёт официальной orb для heroku.

Один таск для запуска всех триггеров

Было бы клёво сделать один таск, который запускает все триггеры, а не лазить каждый раз в settings.py.

Для начала делаем реестр триггеров. Можно через BaseTrigger.__subclasses__(), но лучше через фабрику и регистрацию, как тут. Важно — чтобы реестр работал, все файлы с триггерами обязательно должны импортироваться в момент запуска приложения.

Затем у к каждого триггера делаем метод класса, который сам себя запускает, аналог того, что у нас сейчас в тасках. Только запускать нужно не сам триггер, а специальную таску, на вход которой мы даём название триггера и заказ, относительно которого мы его инициируем. Это нужно на случай, если какой-нибудь триггер упадёт — поскольку он будет работать в отдельной таске, он не сломает код, и остальные триггеры сработают.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.