tough-dev-school / education-backend Goto Github PK
View Code? Open in Web Editor NEWDjango-based backend for our learning management system
Home Page: https://tough-dev.school
License: MIT License
Django-based backend for our learning management system
Home Page: https://tough-dev.school
License: MIT License
В #301 мы сделали эндпоинт, который позволяет заходить суперюзеру от имени другого юзера. На фронте соответствующая страница, которая обслуживает этот адрес находится в '/auth/as/:id' (см. роуты).
Нужно через get_absolute_url()
сделать, чтобы на странице юзера в админке открывался фронт от имени нового юзера, т.е. вышеупомянутая страничка фронта.
Кейс: студент меняет имя (сам или через админку), потому что понимает, что в начале плохо заполнил форму. Надо в этом случае перегенерить диплом для него
Чтобы можно было зайти в админке в промо-код и указать галочками, на какой продукт его можно применить
Хочется, чтобы в свойствах промо-кода можно было указать, к какому продукту он применим. И чтобы промо-код не работал для других продуктов.
У нас сейчас есть триггер-догонялка — он пишет письмо от моего имени с вопросом о том, что помешало купить.
Я хочу похожий триггер, но чтобы он случался через 3 дня после покупки каждой записи (не курса), и спрашивал, типа «как вам»? Письмо я напишу сам, с тебя — только триггер.
А то бывает история — купили с почтой [email protected], а пытаются войти с почтой [email protected] и получают ошибку.
Не забыть смигрировать все данные.
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. "
Чтобы можно было в лид-магните указать теги, которые пробрасываются в аудиторию мейлчимпа
Сейчас используем какой-то мутный сервис для отправки сообщения в телегу. Так вышло потому, что когда мы запускались, API телеграма было раскомнадзорено.
Надо отказаться от использования этого сервиса. Заодно, сделать возможность указывать отдельные каналы уведомлений о новых заказах для разных курсов.
Всякий раз перед запуском нового курса приходится обходить старые промокоды и отключать их. Хочу массовую операцию, в админке. чтобы я просто выбрал их все и отключил.
Нужно прочитать вот эту доку и сделать, что там говорят
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)
Сейчас деплой на хероку происходит средствами самого хероку, когда закончились проверки CI в мастер. Надо перевести его в CircleCI, чтобы процесс деплоя был частью общего единого пайплайна. Для этого отлично подойдёт официальной orb для heroku.
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)
Наш фронт ходит во внутреннее API django-markdownx. Надо написать тесты на то, что это API работает: они понадобятся в случае, если мы когда-нибудь решим отказаться от django-markdownx. Если мы выпилим его без тестов, то узнаем о падении только с фронта, а если выпилим и у нас упадут наши тесты — сразу станет понятно, что этот эндпоинт нужен на фронте.
Всякий раз, когда мы публикуем новую версию, нужно закидывать в сентри релиз. Дока тут
Технически, у нас [email protected] и [email protected] — это два разных имейла. Если в базе есть оба имейла, и юзеры логинятся по имейлу, то они могут нарваться на 403, потому что курс оформлен на имейл с большой буквы, а юзер логинится по имейлу с маленькой буквы.
Нужно сделать команду, которая мёрджит таких пользователей (не теряя их покупки) и докрутить UserCreator, чтобы он не создавал таких дублей.
Бывает, что нужно отправить пользователю ещё один комплект записей или что мы там продаём. При этом отменять оплату и добавлять её заново не хочется — собьётся бухглатерия.
Надо в списке массовых операций добавить кнопку «отправить ещё раз», которая будет делать это только для оплаченных заказов.
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)
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates are pending. To force PRs open, click the checkbox below.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.
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
У нас есть механизм, который подписывает всех новых юзеров на список рассылки. Таким образом у меня копится база подписчиков:
Хочу, чтобы параллельно с этим, если процесс покупки всё-таки завершён, то юзеры подписывались ещё на отдельный специфичный контакт-лист для каждого курса. Скажем, если юзер записался на вебинар про tdd, или купил его запись, то он автоматически подписывался ещё на список рассылки для этого вебинара.
То есть в админке у курса делаем поле mailjet_list_id, и если оно есть — в процессе отправки покупки подписываем на него юзера.
За доступом к мейлджету, если надо обращайся в почту.
Мне иногда нужны списки рассылки, чтобы, скажем, напомнить людям проверить папку «спам», или для какой-нибудь маркетинговой активности. Не хочется выгружать это всё вручную.
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)
Новый dependabot не умеет автомёрджить обновления зависимостей в опенсорс-репозиториях, и, судя по его завязке на GitHub actions, никогда не научится.
Надо мигрировать на renovate
Оно было давно написано для интеграции с timepad, сейчас не используется
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,
К примеру d3d08e11-cc4e-4785-a13b-d72189bce161
показывается у её автора три раза.
Там начинают копиться товары, которые мы продаём. Более логичным названием будет products
Нужно взять список линтеров из f213/django и добавить в этот проект
У нас появилась модель Study, которая фиксирует факт обучения студента на определённом курсе.
Нужно сделать так, чтобы связь ответа на домашку со студентом была через эту модель.
В таком случае курс нужно будет определять в момент создания ответа, а метод Answer.get_purchased_course()
потеряет свой смысл
Когда пользователи копируют промо-код (скажем, с рекламы), они могут случайно скопировать пробел в конце или в начале. Бекенд в таком случае ничего не поймёт — всё будет выглядеть так, как будто мы дали неправильный промо-код, и пользователь пойдёт и купит какой-нибудь другой курс.
А то сейчас там очень много бойлерплейта с параметрами
Сейчас все урлы хранятся в одном большом app/urls.py. Надо сделать, чтобы они были разделены по приложениям.
А то она из-за кастомной модели слетела.
Вероятно, пример реализации есть в f213/django
Было бы клёво сделать один таск, который запускает все триггеры, а не лазить каждый раз в settings.py.
Для начала делаем реестр триггеров. Можно через BaseTrigger.__subclasses__()
, но лучше через фабрику и регистрацию, как тут. Важно — чтобы реестр работал, все файлы с триггерами обязательно должны импортироваться в момент запуска приложения.
Затем у к каждого триггера делаем метод класса, который сам себя запускает, аналог того, что у нас сейчас в тасках. Только запускать нужно не сам триггер, а специальную таску, на вход которой мы даём название триггера и заказ, относительно которого мы его инициируем. Это нужно на случай, если какой-нибудь триггер упадёт — поскольку он будет работать в отдельной таске, он не сломает код, и остальные триггеры сработают.
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(
Do not use signals for business logic. Signals are good only for notification purposes.
(C) README.md
Не совсем issue, а скорее вопрос - что не так с Signals? Или речь идёт о какой-то специфичной "business logic"?
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.')
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.