From c043a9c8de776ac9780e755b7b9cd07233313e41 Mon Sep 17 00:00:00 2001 From: gas53 <45990653+GAS53@users.noreply.github.com> Date: Tue, 3 May 2022 10:07:47 +0300 Subject: [PATCH 01/20] Create test --- test | 1 + 1 file changed, 1 insertion(+) create mode 100644 test diff --git a/test b/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/test @@ -0,0 +1 @@ +test From a7397aa1f31748b45decee28fdac6ff78e15dc7b Mon Sep 17 00:00:00 2001 From: gas53 <45990653+GAS53@users.noreply.github.com> Date: Wed, 25 May 2022 14:54:22 +0300 Subject: [PATCH 02/20] lesson_4 is over --- 002_news.json | 106 ++++++++++++++++++ config/settings.py | 1 + db.sqlite3 | Bin 0 -> 180224 bytes mainapp/2_news.json | 106 ++++++++++++++++++ mainapp/fixtures/001_news.json | 81 +++++++++++++ mainapp/migrations/0001_initial.py | 70 ++++++++++++ mainapp/migrations/0002_data_migration.py | 82 ++++++++++++++ mainapp/models.py | 77 ++++++++++++- mainapp/templates/mainapp/courses_detail.html | 7 ++ mainapp/templates/mainapp/news.html | 12 +- mainapp/views.py | 28 ++--- test | 1 + 12 files changed, 548 insertions(+), 23 deletions(-) create mode 100644 002_news.json create mode 100644 db.sqlite3 create mode 100644 mainapp/2_news.json create mode 100644 mainapp/fixtures/001_news.json create mode 100644 mainapp/migrations/0001_initial.py create mode 100644 mainapp/migrations/0002_data_migration.py create mode 100644 mainapp/templates/mainapp/courses_detail.html create mode 100644 test diff --git a/002_news.json b/002_news.json new file mode 100644 index 0000000..e51932a --- /dev/null +++ b/002_news.json @@ -0,0 +1,106 @@ +[ + { + "model": "mainapp.news", + "pk": 1, + "fields": { + "title": "Запуск нового курса по Python", + "preable": "TYJTJ", + "body": "GHJGHJ", + "body_as_markdown": false, + "created": "2022-05-25T09:25:24.176Z", + "updated": "2022-05-25T09:25:24.176Z", + "deleted": false + } + }, + { + "model": "mainapp.news", + "pk": 2, + "fields": { + "title": "News-3", + "preable": "New_preable", + "body": null, + "body_as_markdown": false, + "created": "2022-05-25T09:37:11.994Z", + "updated": "2022-05-25T09:37:11.994Z", + "deleted": false + } + }, + { + "model": "mainapp.news", + "pk": 3, + "fields": { + "title": "Всем руководителям подразделений подключится к Zoom", + "preable": "Все сотрудники подключены к Zoom, поэтому мы видим, что они делают и говорят.", + "body": "Каждый из 5 руководителей будет в одном из своих офисов и использовать это приложение. Мы хотим, чтобы они могли видеть всё, что происходит на сайте, и просматривать предоставляемые отчёты. После того как сотрудники подключат ZOOM, важно, чтобы каждый секретарь, менеджер и прочие подключились ко всем 5 компьютерам.", + "body_as_markdown": false, + "created": "2022-05-25T09:37:11.994Z", + "updated": "2022-05-25T09:37:11.994Z", + "deleted": false + } + }, + { + "model": "mainapp.news", + "pk": 4, + "fields": { + "title": "Сегодня студенты всего мира отмечают праздник", + "preable": "Подробности — внутри...", + "body": "Сегодня студенты всего мира отмечают праздник, который официально признаётся международным, но более распространён в таких странах, как Китай или Япония. Он называется Фестиваль студенческого пирога (Holly Fest) и празднуется каждый год в начале сентября. Этот праздник объединяет студентов во всём мире. Студенты отмечают начало нового учебного года и дарят друг другу подарки. Во многих азиатских странах этот день также знаменует начало нового года по китайскому календарю. День рождения — 9 июля, 23 года", + "body_as_markdown": false, + "created": "2022-05-25T09:37:11.994Z", + "updated": "2022-05-25T09:37:11.994Z", + "deleted": false + } + }, + { + "model": "mainapp.news", + "pk": 5, + "fields": { + "title": "Встречайте нового преподавателя направления DevOps", + "preable": "Дмитрий Шишмарёв работает в IT-бизнесе с 2001 года.", + "body": null, + "body_as_markdown": false, + "created": "2022-05-25T09:37:11.994Z", + "updated": "2022-05-25T09:37:11.994Z", + "deleted": false + } + }, + { + "model": "mainapp.news", + "pk": 6, + "fields": { + "title": "JavaScript снова возглавил рейтинг самых отвратительных языков", + "preable": "И опять, и снова.", + "body": "Рейтинг самых отвратительных языков программирования, составленный британским изданием, возглавляет JavaScript. В опросе приняли участие более 1000 специалистов по разработке ПО, работающих в различных компаниях. При составлении списка учитывались такие свойства языка, как сложность и простота обучения, а также производительность. Специалисты оценивали язык по 10-балльной шкале, где один балл означал «ужасающий», а 10 баллов — «отвратительный».", + "body_as_markdown": false, + "created": "2022-05-25T09:37:11.994Z", + "updated": "2022-05-25T09:37:11.994Z", + "deleted": false + } + }, + { + "model": "mainapp.news", + "pk": 7, + "fields": { + "title": "Запуск нового курса по Python", + "preable": "Мы рады вам сообщить о запуске нового курса по Python для начинающих!", + "body": "Python предназначен для разработки и изучения новых приложений. Мы предлагаем непросто изучить новый язык программирования, но и научиться применять его на практике. Этот курс полезен не только тем, кто хочет научиться программировать, но и тем, кому не хватает времени на выполнение практических заданий. Ещё это полезно тем, у кого нет возможности посещать курсы. Курс состоит из 7 встреч — одно занятие в неделю длительностью 3 часа.\r\nВстречи проводятся по воскресеньям с 14: 00.", + "body_as_markdown": false, + "created": "2022-05-25T09:25:24.176Z", + "updated": "2022-05-25T09:25:24.176Z", + "deleted": false + } + }, + { + "model": "mainapp.news", + "pk": 8, + "fields": { + "title": "Урок по PHP в среду не состоится", + "preable": "Всем, кто не успел купить курс в понедельник, настоятельно рекомендую это сделать по ссылке в конце публикации.", + "body": "Сегодня в Саратове не будет проходить открытый урок по PHP, который должен был состояться в среду на факультете компьютерных наук СГУ. Преподавательница, за которой закреплена аудитория, сказала, что заболела и не придёт. На следующий урок, запланированный на 28 февраля, перенесли всё, кроме курса «Веб-программирование». Об этом сообщили в группе факультета в социальной сети «ВКонтакте». Курс «Веб-программист» состоит из двух частей. Первая — общий курс, проходящий в течение 72 часов.", + "body_as_markdown": false, + "created": "2022-05-25T09:37:11.994Z", + "updated": "2022-05-25T09:37:11.994Z", + "deleted": false + } + } +] \ No newline at end of file diff --git a/config/settings.py b/config/settings.py index cca7b92..4c44120 100644 --- a/config/settings.py +++ b/config/settings.py @@ -28,6 +28,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'mainapp', + 'markdownify.apps.MarkdownifyConfig', ] MIDDLEWARE = [ diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..377c8a29e27743cce01a3406ba881ae3fdee2d7d GIT binary patch literal 180224 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 i@6sr5Ww6*2&P?&Nr|?}ihNIRzNYSd9yH str: + return f"{self.pk} {self.title}" #pk- primary key + + def delete(self, *args): + self.deleted = True + self.save() + + + + +class CoursesManager(models.Manager): + def get_queryset(self): + return super().get_queryset().filter(deleted=False) + + +class Courses(models.Model): + objects = CoursesManager() + name = models.CharField(max_length=256, verbose_name="Name") + description = models.TextField(verbose_name="Description", blank=True, null=True) + description_as_markdown = models.BooleanField(verbose_name="As markdown", default=False) + cost = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Cost", default=0) + cover = models.CharField(max_length=25, default="no_image.svg", verbose_name="Cover") + created = models.DateTimeField(auto_now_add=True, verbose_name="Created") + updated = models.DateTimeField(auto_now=True, verbose_name="Edited") + deleted = models.BooleanField(default=False) + + def __str__(self) -> str: + return f"{self.pk} {self.name}" + + def delete(self, *args): + self.deleted = True + self.save() + +class Lesson(models.Model): + course = models.ForeignKey(Courses, on_delete=models.CASCADE) + num = models.PositiveIntegerField(verbose_name="Lesson number") + title = models.CharField(max_length=256, verbose_name="Name") + description = models.TextField(verbose_name="Description", blank=True, null=True) + description_as_markdown = models.BooleanField(verbose_name="As markdown", default=False) + created = models.DateTimeField(auto_now_add=True, verbose_name="Created", editable=False) + updated = models.DateTimeField(auto_now=True, verbose_name="Edited", editable=False) + deleted = models.BooleanField(default=False) + + def __str__(self): + return f"{self.course.name} | {self.num} | {self.title}" + + def delete(self, *args): + self.deleted = True + self.save() + + class Meta: + ordering = ("course", "num") + +class CourseTeachers(models.Model): + course = models.ManyToManyField(Courses) + name_first = models.CharField(max_length=128, verbose_name="Name") + name_second = models.CharField(max_length=128, verbose_name="Surname") + day_birth = models.DateField(verbose_name="Birth date") + deleted = models.BooleanField(default=False) + + def __str__(self): + return "{0:0>3} {1} {2}".format(self.pk, self.name_second, self.name_first) + + def delete(self, *args): + self.deleted = True + self.save() + diff --git a/mainapp/templates/mainapp/courses_detail.html b/mainapp/templates/mainapp/courses_detail.html new file mode 100644 index 0000000..eb1b9d5 --- /dev/null +++ b/mainapp/templates/mainapp/courses_detail.html @@ -0,0 +1,7 @@ +
+ {% if course_object.description_as_markdown %} + {{ course_object.description|markdownify }} + {% else %} + {{ course_object.description }} + {% endif %} +
\ No newline at end of file diff --git a/mainapp/templates/mainapp/news.html b/mainapp/templates/mainapp/news.html index 6be1d0d..9795605 100644 --- a/mainapp/templates/mainapp/news.html +++ b/mainapp/templates/mainapp/news.html @@ -9,13 +9,15 @@
- {% for i in range %} + {% for item in news_qs %}
-
{{ news_title }}
-
{{ datetime_obj|date:"Y-m-d h-i-s" }}
-

{{i}}

- Подробнее +
{{ item.title }}
+
+ {{ item.created|date:"Y-m-d h-i-s" }} +
+

{{ item.preambule }}

+
{% endfor %} diff --git a/mainapp/views.py b/mainapp/views.py index 6348efc..5b2e44b 100644 --- a/mainapp/views.py +++ b/mainapp/views.py @@ -1,6 +1,7 @@ from multiprocessing import get_context from django.views.generic import View, TemplateView -from datetime import datetime +from mainapp import models as mainapp_models +from django.shortcuts import get_object_or_404 class Index(TemplateView): template_name = 'mainapp/index.html' @@ -8,24 +9,17 @@ class Index(TemplateView): class News(TemplateView): template_name = 'mainapp/news.html' - def get_news(self): - news = [ - 'Их было около дюжины человек, солдат, матросов, недавних вчерашних крестьян, голодных, оборванных, у многих измятые гимнастерки, у некоторых простреленная и обгоревшая одежда. У каждого — револьвер в руке, у кого обрез, у других винтовки', - 'Солдаты всем сразу дали винтовки и стали учить стрелять.', - 'К берегам реки приткнула баржу, и около баржи плавало несколько солдат с винтовками.', - 'RuGPT3: Быстрый как ветер», — кричит с берега Михаил, а ему кричат из толпы ребята, рабочие:', - 'Небашев пошел к порту и постоял около портовых огней. На берегу видны были факелы, освещающие вход в порт и дебаркадеры. Н' - ] - return news - def get_context_data(self, **kwargs): - # Get all previous data context = super().get_context_data(**kwargs) - # Create your own data - context["news_title"] = "Громкий новостной заголовок" - context[ "news_preview"] = "Предварительное описание, которое заинтересует каждого" - context["datetime_obj"] = datetime.now() - context["range"] = self.get_news() + context["news_qs"] = mainapp_models.News.objects.all()[:5] + return context + +class News_full_view(TemplateView): + template_name = "mainapp/news_detail.html" + + def get_context_data(self, pk=None, **kwargs): + context = super().get_context_data(pk=pk, **kwargs) + context["news_object"] = get_object_or_404(mainapp_models.News, pk=pk) return context diff --git a/test b/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/test @@ -0,0 +1 @@ +test From 278bdf960fb7e53c875c600e264b258afa647ac8 Mon Sep 17 00:00:00 2001 From: gas53 <45990653+GAS53@users.noreply.github.com> Date: Thu, 26 May 2022 13:45:16 +0300 Subject: [PATCH 03/20] Update fixtures --- db.sqlite3 | Bin 180224 -> 204800 bytes mainapp/fixtures/001_news.json | 149 +++--- mainapp/fixtures/002_courses.json | 106 ++++ mainapp/fixtures/003_lessons.json | 834 +++++++++++++++++++++++++++++ mainapp/fixtures/004_teachers.json | 93 ++++ 5 files changed, 1104 insertions(+), 78 deletions(-) create mode 100644 mainapp/fixtures/002_courses.json create mode 100644 mainapp/fixtures/003_lessons.json create mode 100644 mainapp/fixtures/004_teachers.json diff --git a/db.sqlite3 b/db.sqlite3 index 377c8a29e27743cce01a3406ba881ae3fdee2d7d..5519b73241a4bedb1e9ab609b2ad635286b6b488 100644 GIT binary patch literal 204800 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 delta 109 zcmZoTz|+vcJwcjPgMooTXQF~VqsGRBMe-u67})t!nD}4vU*X@)U&i0fKZ`$QW1}Dc z<`n%o37Z;N1e#eEY-d@(_{V=yg8&p22rMcP*i^tMu$?i0iCJJXOTiy`xX1#g|MCEG C Date: Fri, 27 May 2022 08:42:18 +0300 Subject: [PATCH 04/20] 50% --- .python-version | 2 +- authapp/__init__.py | 0 authapp/admin.py | 3 + authapp/apps.py | 6 ++ authapp/migrations/0001_initial.py | 45 ++++++++++ authapp/migrations/__init__.py | 0 authapp/models.py | 57 ++++++++++++ authapp/tests.py | 3 + authapp/views.py | 3 + config/settings.py | 9 +- config/urls.py | 5 ++ db.sqlite3 | Bin 204800 -> 176128 bytes mainapp/migrations/0002_data_migration.py | 82 ------------------ .../0002_rename_preable_news_preambule.py | 18 ++++ mainapp/models.py | 2 +- 15 files changed, 149 insertions(+), 86 deletions(-) create mode 100644 authapp/__init__.py create mode 100644 authapp/admin.py create mode 100644 authapp/apps.py create mode 100644 authapp/migrations/0001_initial.py create mode 100644 authapp/migrations/__init__.py create mode 100644 authapp/models.py create mode 100644 authapp/tests.py create mode 100644 authapp/views.py delete mode 100644 mainapp/migrations/0002_data_migration.py create mode 100644 mainapp/migrations/0002_rename_preable_news_preambule.py diff --git a/.python-version b/.python-version index 75fb1c5..0a764a4 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -django_project +env diff --git a/authapp/__init__.py b/authapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authapp/admin.py b/authapp/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/authapp/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/authapp/apps.py b/authapp/apps.py new file mode 100644 index 0000000..ef802c9 --- /dev/null +++ b/authapp/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthappConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'authapp' diff --git a/authapp/migrations/0001_initial.py b/authapp/migrations/0001_initial.py new file mode 100644 index 0000000..8f8d736 --- /dev/null +++ b/authapp/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# Generated by Django 4.0.4 on 2022-05-27 05:38 + +import authapp.models +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='CustomUser', + fields=[ + ('id', models.BigAutoField(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')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('age', models.PositiveIntegerField(blank=True, null=True)), + ('avatar', models.ImageField(blank=True, null=True, upload_to=authapp.models.users_avatars_path)), + ('email', models.CharField(error_messages={'unique': 'A user with that email address already exists.'}, max_length=256, unique=True, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(auto_now_add=True, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/authapp/migrations/__init__.py b/authapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authapp/models.py b/authapp/models.py new file mode 100644 index 0000000..ed87895 --- /dev/null +++ b/authapp/models.py @@ -0,0 +1,57 @@ +from pathlib import Path +from time import time +from django.contrib.auth.base_user import AbstractBaseUser +from django.contrib.auth.models import PermissionsMixin, UserManager +from django.contrib.auth.validators import ASCIIUsernameValidator +from django.core.mail import send_mail +from django.db import models +from django.utils.translation import gettext_lazy as _ + +def users_avatars_path(instance, filename): + num = int(time() * 1000) + suff = Path(filename).suffix + return f"user_{instance.username}/avatars/pic_{num}{suff}" + +class CustomUser(AbstractBaseUser, PermissionsMixin): + username_validator = ASCIIUsernameValidator() + username = models.CharField(_("username"), max_length=150, unique=True, + help_text=_("Required. 150 characters or fewer. Letters, digits and @/./+/-/_only."), + validators=[username_validator], + error_messages={"unique": _("A user with that username already exists."),}, + ) + first_name = models.CharField(_("first name"), max_length=150, blank=True) + last_name = models.CharField(_("last name"), max_length=150, blank=True) + age = models.PositiveIntegerField(blank=True, null=True) + avatar = models.ImageField(upload_to=users_avatars_path, blank=True, null=True) + email = models.CharField(_("email address"), max_length=256, unique=True, + error_messages={"unique": _("A user with that email address already exists."),},) + is_staff = models.BooleanField( + _("staff status"), default=False, help_text=_("Designates whether the user can log into this admin site."),) + is_active = models.BooleanField(_("active"), default=True, help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."),) + date_joined = models.DateTimeField(_("date joined"), auto_now_add=True) + objects = UserManager() + EMAIL_FIELD = "email" + USERNAME_FIELD = "username" + REQUIRED_FIELDS = ["email"] + + class Meta: + verbose_name = _("user") + verbose_name_plural = _("users") + + def clean(self): + super().clean() + self.email = self.__class__.objects.normalize_email(self.email) + + def get_full_name(self): + full_name = "%s %s" % (self.first_name, self.last_name) + return full_name.strip() + def get_short_name(self): + """Return the short name for the user.""" + return self.first_name + + def email_user(self, subject, message, from_email=None, **kwargs): + """Send an email to this user.""" + send_mail(subject, message, from_email, [self.email], **kwargs) + + + \ No newline at end of file diff --git a/authapp/tests.py b/authapp/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/authapp/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/authapp/views.py b/authapp/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/authapp/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/config/settings.py b/config/settings.py index 4c44120..5270801 100644 --- a/config/settings.py +++ b/config/settings.py @@ -29,6 +29,7 @@ 'django.contrib.staticfiles', 'mainapp', 'markdownify.apps.MarkdownifyConfig', + 'authapp.apps.AuthappConfig' ] MIDDLEWARE = [ @@ -77,8 +78,6 @@ # Password validation -# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators - AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', @@ -122,3 +121,9 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +MEDIA_URL = "/media/" +MEDIA_ROOT = BASE_DIR / "media" +AUTH_USER_MODEL = "authapp.CustomUser" +LOGIN_REDIRECT_URL = "mainapp:main_page" +LOGOUT_REDIRECT_URL = "mainapp:main_page" +MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" \ No newline at end of file diff --git a/config/urls.py b/config/urls.py index 8755559..25e3809 100644 --- a/config/urls.py +++ b/config/urls.py @@ -2,9 +2,14 @@ from django.contrib import admin from django.urls import path, include from django.views.generic import RedirectView +from django.conf import settings +from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path("", RedirectView.as_view(url="mainapp/")), path("mainapp/", include("mainapp.urls", namespace='mainapp')), ] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/db.sqlite3 b/db.sqlite3 index 5519b73241a4bedb1e9ab609b2ad635286b6b488..7129a589d6e7df3e63a06e67b482f10f5bf6c7a4 100644 GIT binary patch delta 5124 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 diff --git a/mainapp/migrations/0002_data_migration.py b/mainapp/migrations/0002_data_migration.py deleted file mode 100644 index dd3e09d..0000000 --- a/mainapp/migrations/0002_data_migration.py +++ /dev/null @@ -1,82 +0,0 @@ -from django.db import migrations - -def forwards_func(apps, schema_editor): - # Get model - News = apps.get_model("mainapp", "News") - # Create model's objects - News.objects.create(title="Запуск нового курса по Python", - preambule="Мы рады вам сообщить о запуске нового курса по Python для \ - начинающих!", - body="Python преднатие в неделю длительностью 3 часа.\r\nВстречи проводятся по воскресеньям с 14:00.",) - News.objects.create(title="Урока по PH в конце публикации.", - body="Сегодня в Саратове не будет проходить открытый урок по PHP, \ - который должен был состояться в среду на факультете компьютерных \ - наук СГУ. Преподавательница, за которой закреплена аудитория, \ - сказала что заболела и не придёт. На следующий урок, \ - запланированный на 28 февраля, перенесли всё, кроме курса \ - «Веб-программирование». Об этом сообщили в группе факультета в \ - социальной сети «ВКонтакте». Курс «Веб-программист» состоит из \ - двух частей. Первая — общий курс, идущий в течение 72 часов.",) - News.objects.create( - title="Всем руководителям подразделений подключится к Zoom", - preambule="Все сотрудники подключены к Zoom, поэтому мы видим, что \ - они делают и говорят.", - body="Каждый из 5 руководителей будет в одном из своих офисов с \ - использованием этого приложения. Мы хотим, чтобы они могли видеть\ - всё, \ - что происходит на сайте, просматривать отчёты, которые им \ - предоставляются. После того как сотрудники подключили ZOOM, нам \ - важно, чтобы каждый секретарь, менеджер и прочие были \ - подключены ко всем 5 компьютерам.", - ) - News.objects.create( - title="Сегодня студенты всего мира отмечают праздник", - preambule="Подробности — внутри...", - body="Сегодня студенты всего мира отмечают праздник, который \ - официально признан международным, но более распространён в \ - Таких азиатских странах, как Китай или Япония. Он называется \ - Фестиваль студенческого пирога (Holly Fest) и празднуется каждый \ - год в начале сентября. Этот праздник объединяет студентов во \ - всём мире. Они отмечают начало нового учебного года и дарят \ - друг другу подарки. Во многих азиатских странах этот день также \ - знаменует начало нового года по китайскому календарю. День \ - Рождения: 9 июля, 23 года", - ) - News.objects.create( - title="Встречайте нового преподавателя направления DevOps", - preambule="Дмитрий Шишмарёв работает в IT-бизнесе с 2001 года.", - body="До прихода в компанию «1С-Битрикс» начинал карьеру в качестве \ - системного администратора. В 2009 перешёл на должность \ - DevOps-инженера, где и трудится по сей день, развиваясь в своей \ - сфере. Дмитрий — сертифицированный специалист компании \ - Bitrix. На его счету более 30 успешно реализованных проектов, \ - среди которых: разработка корпоративной системы управления \ - веб-проектами на базе 1C-Bitrix", - ) - News.objects.create( - title="JavaScript снова возглавил рейтинг самых отвратительных \ - языков", - preambule="И опять, и снова.", - body="Рейтинг самых отвратительных языков программирования, \ - составленный британским изданием, возглавляет JavaScript. В опросе \ - приняли участие более 1000 специалистов по разработке ПО, \ - работающих в различных компаниях. При составлении списка \ - учитывались такие свойства языка, как сложность и простота \ - обучения, а также производительность. Специалисты оценивали язык \ - по 10-балльной шкале, где один балл означал «ужасающий», а 10 \ - баллов — «отвратительный».", - ) - -def reverse_func(apps, schema_editor): - # Get model - News = apps.get_model("mainapp", "News") - # Delete objects - News.objects.all().delete() - - -class Migration(migrations.Migration): - dependencies = [("mainapp", "0001_initial"), - ] - operations = [ - migrations.RunPython(forwards_func, reverse_func), - ] \ No newline at end of file diff --git a/mainapp/migrations/0002_rename_preable_news_preambule.py b/mainapp/migrations/0002_rename_preable_news_preambule.py new file mode 100644 index 0000000..c4ab58e --- /dev/null +++ b/mainapp/migrations/0002_rename_preable_news_preambule.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2022-05-27 05:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mainapp', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='news', + old_name='preable', + new_name='preambule', + ), + ] diff --git a/mainapp/models.py b/mainapp/models.py index 60bcb2c..f0100ab 100644 --- a/mainapp/models.py +++ b/mainapp/models.py @@ -2,7 +2,7 @@ class News(models.Model): title = models.CharField(max_length=256, verbose_name='Заголовок') - preable = models.CharField(max_length=1024, verbose_name="Вступление") + preambule = models.CharField(max_length=1024, verbose_name="Вступление") body = models.TextField(blank=True, null=True, verbose_name="Основной текст") body_as_markdown = models.BooleanField(default=False, verbose_name="As markdown") created = models.DateTimeField(auto_now_add=True, verbose_name="Created", editable=False) From 357e08dd5cf640720ded6453b91b1176d9aece39 Mon Sep 17 00:00:00 2001 From: Gleb Sviridoff Date: Sat, 28 May 2022 09:26:15 +0300 Subject: [PATCH 05/20] end lesson_5 --- 002_news.json | 106 ------------------ authapp/templates/includes/messages.html | 34 ++++++ authapp/templates/registration/login.html | 82 ++++++++++++++ .../templates/registration/profile_edit.html | 60 ++++++++++ authapp/templates/registration/register.html | 70 ++++++++++++ authapp/urls.py | 18 +++ authapp/views.py | 99 +++++++++++++++- config/settings.py | 25 +++-- config/urls.py | 6 +- db.sqlite3 | Bin 176128 -> 241664 bytes mainapp/context_processors/example.py | 4 + mainapp/templates/includes/base.html | 6 + mainapp/templates/includes/head_menu.html | 12 +- mainapp/templates/mainapp/news.html | 8 +- mainapp/urls.py | 17 ++- mainapp/views.py | 57 +++++----- test | 1 - 17 files changed, 441 insertions(+), 164 deletions(-) delete mode 100644 002_news.json create mode 100644 authapp/templates/includes/messages.html create mode 100644 authapp/templates/registration/login.html create mode 100644 authapp/templates/registration/profile_edit.html create mode 100644 authapp/templates/registration/register.html create mode 100644 authapp/urls.py delete mode 100644 test diff --git a/002_news.json b/002_news.json deleted file mode 100644 index e51932a..0000000 --- a/002_news.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "model": "mainapp.news", - "pk": 1, - "fields": { - "title": "Запуск нового курса по Python", - "preable": "TYJTJ", - "body": "GHJGHJ", - "body_as_markdown": false, - "created": "2022-05-25T09:25:24.176Z", - "updated": "2022-05-25T09:25:24.176Z", - "deleted": false - } - }, - { - "model": "mainapp.news", - "pk": 2, - "fields": { - "title": "News-3", - "preable": "New_preable", - "body": null, - "body_as_markdown": false, - "created": "2022-05-25T09:37:11.994Z", - "updated": "2022-05-25T09:37:11.994Z", - "deleted": false - } - }, - { - "model": "mainapp.news", - "pk": 3, - "fields": { - "title": "Всем руководителям подразделений подключится к Zoom", - "preable": "Все сотрудники подключены к Zoom, поэтому мы видим, что они делают и говорят.", - "body": "Каждый из 5 руководителей будет в одном из своих офисов и использовать это приложение. Мы хотим, чтобы они могли видеть всё, что происходит на сайте, и просматривать предоставляемые отчёты. После того как сотрудники подключат ZOOM, важно, чтобы каждый секретарь, менеджер и прочие подключились ко всем 5 компьютерам.", - "body_as_markdown": false, - "created": "2022-05-25T09:37:11.994Z", - "updated": "2022-05-25T09:37:11.994Z", - "deleted": false - } - }, - { - "model": "mainapp.news", - "pk": 4, - "fields": { - "title": "Сегодня студенты всего мира отмечают праздник", - "preable": "Подробности — внутри...", - "body": "Сегодня студенты всего мира отмечают праздник, который официально признаётся международным, но более распространён в таких странах, как Китай или Япония. Он называется Фестиваль студенческого пирога (Holly Fest) и празднуется каждый год в начале сентября. Этот праздник объединяет студентов во всём мире. Студенты отмечают начало нового учебного года и дарят друг другу подарки. Во многих азиатских странах этот день также знаменует начало нового года по китайскому календарю. День рождения — 9 июля, 23 года", - "body_as_markdown": false, - "created": "2022-05-25T09:37:11.994Z", - "updated": "2022-05-25T09:37:11.994Z", - "deleted": false - } - }, - { - "model": "mainapp.news", - "pk": 5, - "fields": { - "title": "Встречайте нового преподавателя направления DevOps", - "preable": "Дмитрий Шишмарёв работает в IT-бизнесе с 2001 года.", - "body": null, - "body_as_markdown": false, - "created": "2022-05-25T09:37:11.994Z", - "updated": "2022-05-25T09:37:11.994Z", - "deleted": false - } - }, - { - "model": "mainapp.news", - "pk": 6, - "fields": { - "title": "JavaScript снова возглавил рейтинг самых отвратительных языков", - "preable": "И опять, и снова.", - "body": "Рейтинг самых отвратительных языков программирования, составленный британским изданием, возглавляет JavaScript. В опросе приняли участие более 1000 специалистов по разработке ПО, работающих в различных компаниях. При составлении списка учитывались такие свойства языка, как сложность и простота обучения, а также производительность. Специалисты оценивали язык по 10-балльной шкале, где один балл означал «ужасающий», а 10 баллов — «отвратительный».", - "body_as_markdown": false, - "created": "2022-05-25T09:37:11.994Z", - "updated": "2022-05-25T09:37:11.994Z", - "deleted": false - } - }, - { - "model": "mainapp.news", - "pk": 7, - "fields": { - "title": "Запуск нового курса по Python", - "preable": "Мы рады вам сообщить о запуске нового курса по Python для начинающих!", - "body": "Python предназначен для разработки и изучения новых приложений. Мы предлагаем непросто изучить новый язык программирования, но и научиться применять его на практике. Этот курс полезен не только тем, кто хочет научиться программировать, но и тем, кому не хватает времени на выполнение практических заданий. Ещё это полезно тем, у кого нет возможности посещать курсы. Курс состоит из 7 встреч — одно занятие в неделю длительностью 3 часа.\r\nВстречи проводятся по воскресеньям с 14: 00.", - "body_as_markdown": false, - "created": "2022-05-25T09:25:24.176Z", - "updated": "2022-05-25T09:25:24.176Z", - "deleted": false - } - }, - { - "model": "mainapp.news", - "pk": 8, - "fields": { - "title": "Урок по PHP в среду не состоится", - "preable": "Всем, кто не успел купить курс в понедельник, настоятельно рекомендую это сделать по ссылке в конце публикации.", - "body": "Сегодня в Саратове не будет проходить открытый урок по PHP, который должен был состояться в среду на факультете компьютерных наук СГУ. Преподавательница, за которой закреплена аудитория, сказала, что заболела и не придёт. На следующий урок, запланированный на 28 февраля, перенесли всё, кроме курса «Веб-программирование». Об этом сообщили в группе факультета в социальной сети «ВКонтакте». Курс «Веб-программист» состоит из двух частей. Первая — общий курс, проходящий в течение 72 часов.", - "body_as_markdown": false, - "created": "2022-05-25T09:37:11.994Z", - "updated": "2022-05-25T09:37:11.994Z", - "deleted": false - } - } -] \ No newline at end of file diff --git a/authapp/templates/includes/messages.html b/authapp/templates/includes/messages.html new file mode 100644 index 0000000..7c5b1ad --- /dev/null +++ b/authapp/templates/includes/messages.html @@ -0,0 +1,34 @@ +
+
+ +
+ {% for message in messages %} + + + {% if message.level > 20 %} + + + {% endif %} + {% endfor %} +
+
+
\ No newline at end of file diff --git a/authapp/templates/registration/login.html b/authapp/templates/registration/login.html new file mode 100644 index 0000000..9888fed --- /dev/null +++ b/authapp/templates/registration/login.html @@ -0,0 +1,82 @@ +{% load static %} + + + + + + + + + + + + + + Welcome to Braniac! + + + + {% include 'includes/messages.html' %} +
+
+

+ +

+

Вход пользователя

+
+ {% csrf_token %} +

+ +

+

+ +

+

+ +

+
+

+

+

+

Вход через социальные сети

+

+

+ +
+

+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/authapp/templates/registration/profile_edit.html b/authapp/templates/registration/profile_edit.html new file mode 100644 index 0000000..58bdf11 --- /dev/null +++ b/authapp/templates/registration/profile_edit.html @@ -0,0 +1,60 @@ +{% block content %} +
+
+

Редактирование профиля

+
+
+ {% if user.avatar %} + + {% else %} + + {% endif %} +
+
+
+ {% csrf_token %} +
+
+ Required. 150 characters or fewer. + Letters, digits and @/./+/-/_ only. +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+{% endblock content %} \ No newline at end of file diff --git a/authapp/templates/registration/register.html b/authapp/templates/registration/register.html new file mode 100644 index 0000000..ef0c5b6 --- /dev/null +++ b/authapp/templates/registration/register.html @@ -0,0 +1,70 @@ +{% block content %} +
+
+

Регистрация нового пользователя

+
+ {% csrf_token %} +
+
Required. 150 characters or fewer. + Letters, digits and @/./+/-/_ only.
+
+
+
+
    +
  • Your password can’t be too similar to your other personal + information.
  • +
  • Your password must contain at least 8 characters.
  • +
  • Your password can’t be a commonly used password.
  • +
  • Your password can’t be entirely numeric.
  • +
+
+
+
+ +
Enter the same password as before, for + verification.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+{% endblock content %} \ No newline at end of file diff --git a/authapp/urls.py b/authapp/urls.py new file mode 100644 index 0000000..d3f5b31 --- /dev/null +++ b/authapp/urls.py @@ -0,0 +1,18 @@ +from django.urls import path +from authapp import views +from authapp.apps import AuthappConfig +from django.urls import include, path +from django.views.generic import RedirectView +from django.conf import settings +from django.conf.urls.static import static +from django.contrib import admin + + +app_name = AuthappConfig.name + +urlpatterns = [ + path("login/", views.CustomLoginView.as_view(), name="login"), + path("logout/", views.CustomLogoutView.as_view(), name="logout"), + path("register/", views.RegisterView.as_view(), name="register"), + path("profile_edit/", views.ProfileEditView.as_view(), name="profile_edit"), +] \ No newline at end of file diff --git a/authapp/views.py b/authapp/views.py index 91ea44a..cf6c70f 100644 --- a/authapp/views.py +++ b/authapp/views.py @@ -1,3 +1,100 @@ from django.shortcuts import render +from django.contrib.auth.views import LoginView, LogoutView +from django.contrib import messages +from authapp import models +from django.views.generic import TemplateView +from django.contrib.auth.mixins import LoginRequiredMixin +from django.utils.safestring import mark_safe +from django.urls import reverse_lazy +import os +from django.http import HttpResponseRedirect -# Create your views here. +class CustomLoginView(LoginView): + def form_valid(self, form): + ret = super().form_valid(form) + message = ("Login success!
Hi, %(username)s") % {"username": self.request.user.get_full_name() + if self.request.user.get_full_name() + else self.request.user.get_username() + } + messages.add_message(self.request, messages.INFO, mark_safe(message)) + return ret + + def form_invalid(self, form): + for _unused, msg in form.error_messages.items(): + messages.add_message(self.request, messages.WARNING, mark_safe(f"Something goes worng:
{msg}"),) + return self.render_to_response(self.get_context_data(form=form)) + +class CustomLogoutView(LogoutView): + def dispatch(self, request, *args, **kwargs): + messages.add_message(self.request, messages.INFO, ("See you later!")) + return super().dispatch(request, *args, **kwargs) + + +class RegisterView(TemplateView): + template_name = "registration/register.html" + + def post(self, request, *args, **kwargs): + try: + if all( + ( + request.POST.get("username"), + request.POST.get("email"), + request.POST.get("password1"), + request.POST.get("password1")== request.POST.get("password2"), + ) + ): + new_user = models.CustomUser.objects.create( + username=request.POST.get("username"), + first_name=request.POST.get("first_name"), + last_name=request.POST.get("last_name"), + age=request.POST.get("age") + if request.POST.get("age") + else 0, + avatar=request.FILES.get("avatar"), + email=request.POST.get("email"), + ) + new_user.set_password(request.POST.get("password1")) + new_user.save() + messages.add_message( + request, messages.INFO, _("Registration success!") + ) + return HttpResponseRedirect(reverse_lazy("authapp:login")) + + except Exception as exp: + messages.add_message( + request, + messages.WARNING, + mark_safe(f"Something goes worng:
{exp}"), + ) + return HttpResponseRedirect(reverse_lazy("authapp:register")) + + +class ProfileEditView(LoginRequiredMixin, TemplateView): + template_name = "registration/profile_edit.html" + login_url = reverse_lazy("authapp:login") + + def post(self, request, *args, **kwargs): + try: + if request.POST.get("username"): + request.user.username = request.POST.get("username") + if request.POST.get("first_name"): + request.user.first_name = request.POST.get("first_name") + if request.POST.get("last_name"): + request.user.last_name = request.POST.get("last_name") + if request.POST.get("age"): + request.user.age = request.POST.get("age") + if request.POST.get("email"): + request.user.email = request.POST.get("email") + if request.FILES.get("avatar"): + if request.user.avatar and os.path.exists( + request.user.avatar.path): + os.remove(request.user.avatar.path) + request.user.avatar = request.FILES.get("avatar") + request.user.save() + messages.add_message(request, messages.INFO, _("Saved!")) + except Exception as exp: + messages.add_message( + request, + messages.WARNING, + mark_safe(f"Something goes worng:
{exp}"),) + return HttpResponseRedirect(reverse_lazy("authapp:profile_edit")) \ No newline at end of file diff --git a/config/settings.py b/config/settings.py index 5270801..7b9244e 100644 --- a/config/settings.py +++ b/config/settings.py @@ -29,7 +29,8 @@ 'django.contrib.staticfiles', 'mainapp', 'markdownify.apps.MarkdownifyConfig', - 'authapp.apps.AuthappConfig' + 'authapp.apps.AuthappConfig', + 'social_django', ] MIDDLEWARE = [ @@ -51,11 +52,14 @@ 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - # "mainapp.context_processors.example.simple_context_processor", + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.template.context_processors.media", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "mainapp.context_processors.example.simple_context_processor", + "social_django.context_processors.backends", + "social_django.context_processors.login_redirect", ], 'libraries':{'email_to_link':'mainapp.context_processors.example'} @@ -126,4 +130,11 @@ AUTH_USER_MODEL = "authapp.CustomUser" LOGIN_REDIRECT_URL = "mainapp:main_page" LOGOUT_REDIRECT_URL = "mainapp:main_page" -MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" \ No newline at end of file +MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" +AUTHENTICATION_BACKENDS = ( + "social_core.backends.github.GithubOAuth2", + "django.contrib.auth.backends.ModelBackend", + ) + +SOCIAL_AUTH_GITHUB_KEY = '8b4874c2b4c54df66fe3' +SOCIAL_AUTH_GITHUB_SECRET = 'eb18d35d54c609868f9791cf57374bdd8f66605e' \ No newline at end of file diff --git a/config/urls.py b/config/urls.py index 25e3809..939e903 100644 --- a/config/urls.py +++ b/config/urls.py @@ -9,7 +9,9 @@ path('admin/', admin.site.urls), path("", RedirectView.as_view(url="mainapp/")), path("mainapp/", include("mainapp.urls", namespace='mainapp')), -] + path("authapp/", include("authapp.urls", namespace="authapp")), + path("social_auth/", include("social_django.urls", namespace='social')) + ] if settings.DEBUG: - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 7129a589d6e7df3e63a06e67b482f10f5bf6c7a4..ecadd2ed2a0690d93ae9c5dd27e5448d0ba9f9d8 100644 GIT binary patch delta 7292 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 delta 607 zcmY*VO=uHA6rQ&;o6T;rc^ktv#UkAtY9cASmEM$?K&XYfqx|B2iE;(M$25L4RLq&apfv(P`NCy6;$6geee+ZXz98{2NQJcp64ef+M+RsU2LZAnYJ31eDu7k5vJ zF?>TrtPxznEv(@~bnrTVHH~qL#28IO>B{VdGv%|FXQnEZsY@4AwqqUW(fv^z|5u#S zL2wpML2Qqra7jUn;5U4YAJD?bIERO^gtK_tjZVbSwz6-#6*LKc#rOCMAK@*$ffby< zAvAf%F0{>~?`SH#W=qFLx#9C+ckT2Lb=lId-lqmIjiykyrMj=JMqK@FD@Rju!9ny)*U?UikMgs}aVj19Iti)vfynMg%b5X<%OGqw|*QpfT_PpmM1&tm_^~4^SvNs WeAEAodoNDHHgtSge6w+HS^En;bDNt0 diff --git a/mainapp/context_processors/example.py b/mainapp/context_processors/example.py index d6969ed..06cfbe9 100644 --- a/mainapp/context_processors/example.py +++ b/mainapp/context_processors/example.py @@ -6,3 +6,7 @@ @register.filter def email_to_link(value): return mark_safe(f"{value}") + + +def simple_context_processor(request): + return {"foo": "bar"} \ No newline at end of file diff --git a/mainapp/templates/includes/base.html b/mainapp/templates/includes/base.html index 983bef1..a4ec21e 100644 --- a/mainapp/templates/includes/base.html +++ b/mainapp/templates/includes/base.html @@ -13,6 +13,12 @@ + {% if user.is_authenticated %} +
  • Выйти
  • + % else % +
  • Войти
  • + {% endif %} + {% include 'includes/head_menu.html' %} {% block content %} diff --git a/mainapp/templates/includes/head_menu.html b/mainapp/templates/includes/head_menu.html index a921130..5e31d96 100644 --- a/mainapp/templates/includes/head_menu.html +++ b/mainapp/templates/includes/head_menu.html @@ -12,12 +12,12 @@ diff --git a/mainapp/templates/mainapp/news.html b/mainapp/templates/mainapp/news.html index 9795605..2be174c 100644 --- a/mainapp/templates/mainapp/news.html +++ b/mainapp/templates/mainapp/news.html @@ -57,10 +57,10 @@
    diff --git a/mainapp/urls.py b/mainapp/urls.py index b20583d..9db8c80 100644 --- a/mainapp/urls.py +++ b/mainapp/urls.py @@ -8,12 +8,11 @@ urlpatterns = [ - path("", views.Index.as_view(), name="main_page"), - path("news/", views.News.as_view(), name="news"), - path("news//", views.NewsWithPaginatorView.as_view(), name="news_paginator"), - - path("courses/", views.Courses.as_view(), name="courses"), - path("contacts/", views.Contacts.as_view(), name="contacts"), - path("doc_site/", views.Doc.as_view(), name="doc_site"), - path("login/", views.Login.as_view(), name="login"), -] \ No newline at end of file + path("", views.MainPageView.as_view(), name="main_page"), + path("news/", views.NewsPageView.as_view(), name="news"), + path("news//", views.NewsPageDetailView.as_view(), name="news_detail"), + path("courses/", views.CoursesListView.as_view(), name="courses"), + path("courses//", views.CoursesDetailView.as_view(), name="courses_detail",), + path("contacts/", views.ContactsPageView.as_view(), name="contacts"), + path("doc_site/", views.DocSitePageView.as_view(), name="doc_site"), + ] \ No newline at end of file diff --git a/mainapp/views.py b/mainapp/views.py index 5b2e44b..d6511c0 100644 --- a/mainapp/views.py +++ b/mainapp/views.py @@ -1,22 +1,23 @@ -from multiprocessing import get_context -from django.views.generic import View, TemplateView from mainapp import models as mainapp_models from django.shortcuts import get_object_or_404 +from django.views.generic import TemplateView -class Index(TemplateView): - template_name = 'mainapp/index.html' -class News(TemplateView): - template_name = 'mainapp/news.html' +class MainPageView(TemplateView): + template_name = "mainapp/index.html" + +class NewsPageView(TemplateView): + template_name = "mainapp/news.html" + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["news_qs"] = mainapp_models.News.objects.all()[:5] return context -class News_full_view(TemplateView): +class NewsPageDetailView(TemplateView): template_name = "mainapp/news_detail.html" - + def get_context_data(self, pk=None, **kwargs): context = super().get_context_data(pk=pk, **kwargs) context["news_object"] = get_object_or_404(mainapp_models.News, pk=pk) @@ -24,29 +25,29 @@ def get_context_data(self, pk=None, **kwargs): -class NewsWithPaginatorView(News): - def get_context_data(self, page, **kwargs): - context = super().get_context_data(page=page, **kwargs) - context["page_num"] = page - return context - - - -class Contacts(TemplateView): - template_name = 'mainapp/contacts.html' - -class Courses(TemplateView): - template_name = 'mainapp/courses_list.html' - -class Doc(TemplateView): - template_name = 'mainapp/doc_site.html' - -class Login(TemplateView): - template_name = 'mainapp/login.html' - +class CoursesListView(TemplateView): + template_name = "mainapp/courses_list.html" + def get_context_data(self, **kwargs): + context = super(CoursesListView, self).get_context_data(**kwargs) + context["objects"] = mainapp_models.Courses.objects.all()[:7] + return context +class CoursesDetailView(TemplateView): + template_name = "mainapp/courses_detail.html" + def get_context_data(self, pk=None, **kwargs): + context = super(CoursesDetailView, self).get_context_data(**kwargs) + context["course_object"] = get_object_or_404(mainapp_models.Courses, pk=pk) + context["lessons"] = mainapp_models.Lesson.objects.filter( + course=context["course_object"]) + context["teachers"] = mainapp_models.CourseTeachers.objects.filter( + course=context["course_object"]) + return context +class ContactsPageView(TemplateView): + template_name = "mainapp/contacts.html" +class DocSitePageView(TemplateView): + template_name = "mainapp/doc_site.html" \ No newline at end of file diff --git a/test b/test deleted file mode 100644 index 9daeafb..0000000 --- a/test +++ /dev/null @@ -1 +0,0 @@ -test From 853cd62e9d612cd18ae2dd83fc1080054c6fb67f Mon Sep 17 00:00:00 2001 From: Gleb Sviridoff Date: Mon, 30 May 2022 10:30:28 +0300 Subject: [PATCH 06/20] lesson_5.2 --- authapp/admin.py | 13 ++++++++++++- authapp/forms.py | 14 ++++++++++++++ authapp/models.py | 2 +- config/settings.py | 7 +++++-- db.sqlite3 | Bin 241664 -> 241664 bytes mainapp/templates/includes/head_menu.html | 6 ++++++ 6 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 authapp/forms.py diff --git a/authapp/admin.py b/authapp/admin.py index 8c38f3f..10ac7af 100644 --- a/authapp/admin.py +++ b/authapp/admin.py @@ -1,3 +1,14 @@ from django.contrib import admin +from django.contrib.auth import get_user_model +from django.contrib.auth.admin import UserAdmin -# Register your models here. +from .forms import CustomUserCreationForm, CustomUserChangeForm +from .models import CustomUser + +class CustomUserAdmin(UserAdmin): + add_form = CustomUserCreationForm + form = CustomUserChangeForm + model = CustomUser + list_display = ['email', 'username',] + +admin.site.register(CustomUser, CustomUserAdmin) \ No newline at end of file diff --git a/authapp/forms.py b/authapp/forms.py new file mode 100644 index 0000000..b4f5315 --- /dev/null +++ b/authapp/forms.py @@ -0,0 +1,14 @@ +from django.contrib.auth.forms import UserCreationForm, UserChangeForm +from .models import CustomUser + +class CustomUserCreationForm(UserCreationForm): + + class Meta: + model = CustomUser + fields = ('username', 'email') + +class CustomUserChangeForm(UserChangeForm): + + class Meta: + model = CustomUser + fields = ('username', 'email') \ No newline at end of file diff --git a/authapp/models.py b/authapp/models.py index ed87895..be0b515 100644 --- a/authapp/models.py +++ b/authapp/models.py @@ -6,6 +6,7 @@ from django.core.mail import send_mail from django.db import models from django.utils.translation import gettext_lazy as _ +from django.contrib.auth.models import AbstractUser def users_avatars_path(instance, filename): num = int(time() * 1000) @@ -54,4 +55,3 @@ def email_user(self, subject, message, from_email=None, **kwargs): send_mail(subject, message, from_email, [self.email], **kwargs) - \ No newline at end of file diff --git a/config/settings.py b/config/settings.py index 7b9244e..7474e25 100644 --- a/config/settings.py +++ b/config/settings.py @@ -10,7 +10,7 @@ # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-)8%fdb3%51xymravhh$zl#u^3#5dmipoui(^f@v##8x#8hmf%^' +SECRET_KEY = os.environ.get('django_secret_key') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -31,6 +31,8 @@ 'markdownify.apps.MarkdownifyConfig', 'authapp.apps.AuthappConfig', 'social_django', + + ] MIDDLEWARE = [ @@ -137,4 +139,5 @@ ) SOCIAL_AUTH_GITHUB_KEY = '8b4874c2b4c54df66fe3' -SOCIAL_AUTH_GITHUB_SECRET = 'eb18d35d54c609868f9791cf57374bdd8f66605e' \ No newline at end of file +SOCIAL_AUTH_GITHUB_SECRET = os.environ.get('django_git_password') +AUTH_USER_MODEL = 'authapp.CustomUser' # new \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index ecadd2ed2a0690d93ae9c5dd27e5448d0ba9f9d8..4c08d5e15699ac7f2bea156b32e9dd918d5f3a10 100644 GIT binary patch delta 340 zcmZp8z}N7AZ-O+V_e2?IM(>RY%j89j8TfbeKj&}aU&BA0{}R79e=@)EX2Aqreog*l z1{DS<5aVYyWK1kA$w(|Hh)*spF3Ha=Elw?B660nz=9u2t#w@%!S%0Ac2NU0Q2L5cm z>zfr7PV?0_3NrCCG}8yNtBijhaAx0gj# zSz<=IPo9%^fNzMOYfh+2N@;kBNm`dSbDuF_>^j%GN6?H8L6K+Q~Ni5 OMj&R|{*9lRYXJZsbYb!U delta 156 zcmZp8z}N7AZ-O+V*F+g-Mz4(t%j88&82ESdKj&}aU&BA0{}R79e=@(xX2Akpeog*l z1{DSv5ank!G<7PJInBLgNEW9~cf1v>zBY!pne>PBm yBLC#<{Fy-hPX_*uf|HjYEwEzIwSumRb diff --git a/mainapp/templates/includes/head_menu.html b/mainapp/templates/includes/head_menu.html index 5e31d96..608b8e7 100644 --- a/mainapp/templates/includes/head_menu.html +++ b/mainapp/templates/includes/head_menu.html @@ -40,6 +40,12 @@ class="nav-item {% if request.resolver_match.view_name == 'mainapp:mainapp.views.ContactsPageView' %}active{% endif %}"> Контакты + 🇷🇺 🇬🇧 From 22eee9e1aac026c493a06affb46ccf3b7ad6798e Mon Sep 17 00:00:00 2001 From: Gleb Sviridoff Date: Mon, 30 May 2022 14:50:18 +0300 Subject: [PATCH 07/20] for lesson_6 --- authapp/forms.py | 59 +++++++++++-- authapp/models.py | 34 ++++---- .../admin/authapp/customers_form.html | 33 +++++++ authapp/templates/admin/base_site.html | 12 +++ authapp/templates/registration/register.html | 66 ++------------ authapp/urls.py | 2 +- authapp/views.py | 81 ++++-------------- config/settings.py | 6 +- db.sqlite3 | Bin 241664 -> 241664 bytes mainapp/admin.py | 21 ++++- mainapp/migrations/0003_alter_news_options.py | 17 ++++ mainapp/models.py | 12 +++ mainapp/templates/includes/head_menu.html | 6 +- 13 files changed, 192 insertions(+), 157 deletions(-) create mode 100644 authapp/templates/admin/authapp/customers_form.html create mode 100644 authapp/templates/admin/base_site.html create mode 100644 mainapp/migrations/0003_alter_news_options.py diff --git a/authapp/forms.py b/authapp/forms.py index b4f5315..2ddb225 100644 --- a/authapp/forms.py +++ b/authapp/forms.py @@ -1,14 +1,59 @@ -from django.contrib.auth.forms import UserCreationForm, UserChangeForm +from django.contrib.auth.forms import UserCreationForm, UsernameField, UserChangeForm from .models import CustomUser +from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError +import os +from django import forms -class CustomUserCreationForm(UserCreationForm): +class CustomUserChangeForm(forms.ModelForm): class Meta: - model = CustomUser - fields = ('username', 'email') + model = get_user_model() + fields = ( + "username", + "email", + "first_name", + "last_name", + "age", + "avatar", + ) + field_classes = {"username": UsernameField} + + def clean_avatar(self): + arg_as_str = "avatar" + if arg_as_str in self.changed_data and self.instance.avatar: + if os.path.exists(self.instance.avatar.path): + os.remove(self.instance.avatar.path) + return self.cleaned_data.get(arg_as_str) + + def clean_age(self): + data = self.cleaned_data.get("age") + if data < 10 or data > 100: + raise ValidationError(_("Please, enter a valid age!")) + return data + + -class CustomUserChangeForm(UserChangeForm): +class CustomUserCreationForm(UserCreationForm): + field_order = [ + "username", + "password1", + "password2", + + "first_name", + "last_name", + "age", + "avatar", + ] class Meta: - model = CustomUser - fields = ('username', 'email') \ No newline at end of file + model = get_user_model() + fields = ( + "username", + + "first_name", + "last_name", + "age", + "avatar", + ) + field_classes = {"username": UsernameField} diff --git a/authapp/models.py b/authapp/models.py index be0b515..e38b62a 100644 --- a/authapp/models.py +++ b/authapp/models.py @@ -1,5 +1,7 @@ from pathlib import Path +from re import T from time import time +from wsgiref.validate import validator from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.models import PermissionsMixin, UserManager from django.contrib.auth.validators import ASCIIUsernameValidator @@ -15,25 +17,19 @@ def users_avatars_path(instance, filename): class CustomUser(AbstractBaseUser, PermissionsMixin): username_validator = ASCIIUsernameValidator() - username = models.CharField(_("username"), max_length=150, unique=True, - help_text=_("Required. 150 characters or fewer. Letters, digits and @/./+/-/_only."), - validators=[username_validator], - error_messages={"unique": _("A user with that username already exists."),}, - ) - first_name = models.CharField(_("first name"), max_length=150, blank=True) - last_name = models.CharField(_("last name"), max_length=150, blank=True) - age = models.PositiveIntegerField(blank=True, null=True) - avatar = models.ImageField(upload_to=users_avatars_path, blank=True, null=True) - email = models.CharField(_("email address"), max_length=256, unique=True, - error_messages={"unique": _("A user with that email address already exists."),},) - is_staff = models.BooleanField( - _("staff status"), default=False, help_text=_("Designates whether the user can log into this admin site."),) - is_active = models.BooleanField(_("active"), default=True, help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."),) - date_joined = models.DateTimeField(_("date joined"), auto_now_add=True) - objects = UserManager() - EMAIL_FIELD = "email" - USERNAME_FIELD = "username" - REQUIRED_FIELDS = ["email"] + username = models.CharField( + _("username"), + max_length=150, + unique=True, + help_text=_('150 символов и цифр включая @ . + - _' + ), + validators = [username_validator], + error_messages = { + 'unique': _('Пользователь с таким ником уже существует') + }, + ) + first_name = models.CharField(_('Имя'), max_length=150, blank=True) + last_name = models.CharField(_('Фамилия'), max_length=150, blank=True) class Meta: verbose_name = _("user") diff --git a/authapp/templates/admin/authapp/customers_form.html b/authapp/templates/admin/authapp/customers_form.html new file mode 100644 index 0000000..e356b7d --- /dev/null +++ b/authapp/templates/admin/authapp/customers_form.html @@ -0,0 +1,33 @@ +{% extends 'base.html' %} +{% load crispy_forms_tags %} +{% block content %} +
    +
    + {% if user.is_anonymous %} +

    Регистрация нового пользователя

    + {% else %} +

    Редактировать профиль

    +
    +
    + {% if user.avatar %} + + {% else %} + + {% endif %} +
    +
    + {% endif %} +
    + {% csrf_token %} + {{ form|crispy }} + +
    +
    +
    +{% endblock content %} \ No newline at end of file diff --git a/authapp/templates/admin/base_site.html b/authapp/templates/admin/base_site.html new file mode 100644 index 0000000..1b2aa42 --- /dev/null +++ b/authapp/templates/admin/base_site.html @@ -0,0 +1,12 @@ +{% extends "includes/base.html" %} +{% load static %} +{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ +site_title|default:_('Django site admin') }}{% endblock %} +{% block branding %} +

    + + + +

    +{% endblock %} +{% block nav-global %}{% endblock %} \ No newline at end of file diff --git a/authapp/templates/registration/register.html b/authapp/templates/registration/register.html index ef0c5b6..3345398 100644 --- a/authapp/templates/registration/register.html +++ b/authapp/templates/registration/register.html @@ -1,70 +1,14 @@ +{% load crispy_forms_tags %} {% block content %}

    Регистрация нового пользователя

    {% csrf_token %} -
    -
    Required. 150 characters or fewer. - Letters, digits and @/./+/-/_ only.
    -
    -
    -
    -
      -
    • Your password can’t be too similar to your other personal - information.
    • -
    • Your password must contain at least 8 characters.
    • -
    • Your password can’t be a commonly used password.
    • -
    • Your password can’t be entirely numeric.
    • -
    -
    -
    -
    - -
    Enter the same password as before, for - verification.
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    + {{ form.as_p }}
    + + +
    {% endblock content %} \ No newline at end of file diff --git a/authapp/urls.py b/authapp/urls.py index d3f5b31..3372245 100644 --- a/authapp/urls.py +++ b/authapp/urls.py @@ -14,5 +14,5 @@ path("login/", views.CustomLoginView.as_view(), name="login"), path("logout/", views.CustomLogoutView.as_view(), name="logout"), path("register/", views.RegisterView.as_view(), name="register"), - path("profile_edit/", views.ProfileEditView.as_view(), name="profile_edit"), + path("profile_edit//", views.ProfileEditView.as_view(), name="profile_edit"), ] \ No newline at end of file diff --git a/authapp/views.py b/authapp/views.py index cf6c70f..2eccb70 100644 --- a/authapp/views.py +++ b/authapp/views.py @@ -1,5 +1,7 @@ +from .forms import CustomUserCreationForm, CustomUserChangeForm from django.shortcuts import render from django.contrib.auth.views import LoginView, LogoutView +from django.views.generic.edit import CreateView from django.contrib import messages from authapp import models from django.views.generic import TemplateView @@ -8,6 +10,9 @@ from django.urls import reverse_lazy import os from django.http import HttpResponseRedirect +from django.contrib.auth import get_user_model + + class CustomLoginView(LoginView): def form_valid(self, form): @@ -30,71 +35,17 @@ def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) -class RegisterView(TemplateView): - template_name = "registration/register.html" - - def post(self, request, *args, **kwargs): - try: - if all( - ( - request.POST.get("username"), - request.POST.get("email"), - request.POST.get("password1"), - request.POST.get("password1")== request.POST.get("password2"), - ) - ): - new_user = models.CustomUser.objects.create( - username=request.POST.get("username"), - first_name=request.POST.get("first_name"), - last_name=request.POST.get("last_name"), - age=request.POST.get("age") - if request.POST.get("age") - else 0, - avatar=request.FILES.get("avatar"), - email=request.POST.get("email"), - ) - new_user.set_password(request.POST.get("password1")) - new_user.save() - messages.add_message( - request, messages.INFO, _("Registration success!") - ) - return HttpResponseRedirect(reverse_lazy("authapp:login")) - - except Exception as exp: - messages.add_message( - request, - messages.WARNING, - mark_safe(f"Something goes worng:
    {exp}"), - ) - return HttpResponseRedirect(reverse_lazy("authapp:register")) +class RegisterView(CreateView): + model = get_user_model() + form_class = CustomUserCreationForm + success_url = reverse_lazy("mainapp:main_page") +class ProfileEditView(UserPassesTestMixin, UpdateView): + model = get_user_model() + form_class = CustomUserChangeForm -class ProfileEditView(LoginRequiredMixin, TemplateView): - template_name = "registration/profile_edit.html" - login_url = reverse_lazy("authapp:login") + def test_func(self): + return True if self.request.user.pk == self.kwargs.get("pk") else False - def post(self, request, *args, **kwargs): - try: - if request.POST.get("username"): - request.user.username = request.POST.get("username") - if request.POST.get("first_name"): - request.user.first_name = request.POST.get("first_name") - if request.POST.get("last_name"): - request.user.last_name = request.POST.get("last_name") - if request.POST.get("age"): - request.user.age = request.POST.get("age") - if request.POST.get("email"): - request.user.email = request.POST.get("email") - if request.FILES.get("avatar"): - if request.user.avatar and os.path.exists( - request.user.avatar.path): - os.remove(request.user.avatar.path) - request.user.avatar = request.FILES.get("avatar") - request.user.save() - messages.add_message(request, messages.INFO, _("Saved!")) - except Exception as exp: - messages.add_message( - request, - messages.WARNING, - mark_safe(f"Something goes worng:
    {exp}"),) - return HttpResponseRedirect(reverse_lazy("authapp:profile_edit")) \ No newline at end of file + def get_success_url(self): + return reverse_lazy("authapp:profile_edit", args=[self.request.user.pk]) diff --git a/config/settings.py b/config/settings.py index 7474e25..6af54fe 100644 --- a/config/settings.py +++ b/config/settings.py @@ -10,7 +10,7 @@ # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get('django_secret_key') +SECRET_KEY = os.environ.get('django_key') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -31,6 +31,7 @@ 'markdownify.apps.MarkdownifyConfig', 'authapp.apps.AuthappConfig', 'social_django', + 'crispy_forms', ] @@ -140,4 +141,5 @@ SOCIAL_AUTH_GITHUB_KEY = '8b4874c2b4c54df66fe3' SOCIAL_AUTH_GITHUB_SECRET = os.environ.get('django_git_password') -AUTH_USER_MODEL = 'authapp.CustomUser' # new \ No newline at end of file +AUTH_USER_MODEL = 'authapp.CustomUser' +CRISPY_TEMPLATE_PACK = "bootstrap4" \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 4c08d5e15699ac7f2bea156b32e9dd918d5f3a10..3eddc5ce282998401934995fd426954813f9af97 100644 GIT binary patch delta 2600 zcmai#OKjWr9mhpVvg{<%-^H2@ZR$nI<^f(H<%AZRH0A=#E4NmgVh>r_1`>PbqZ zB}ys^)y|3y=&&vIAp>?;5fnRR=n}*8+92(r$6lmdHeegpZWy+mh8@;Rd)S|p=V!7J z(vJjx{C|G0FaLbwQ_mZpdVX>a>)v@OgmoXh`Yy0fIk&c4<1yU1wVfFkoG)LUa@x*a z5o?)}lBw4rgj70HU6-`9A{lyGt*@6W%ptE zaz>HWbS7UbDe0;zkByJpC-)yU({OH4m-Aah&I<@u}U9Z$w{-u-v?4l4U+?jA8V zW)r~$H-=$Hizdhb!=R#hih>m7rMv~Xh<+~I&WxH%rk=V4a()O|nx#n6+ke>qsQ(*e zf7SnJ1h-#vnajR$3B$$?BK5pb2tX!4Q+|>|p*V)|<_mH`F3ZL8t=BGQ@_8wLG2nge z_19l<;a5jY#Kcj=9)Y6$G^7bg(6m=86cOUY@{rR?{P6PLP@oKhlJK)6WC`wA=Eo=_ z_{$EHa^Wy#4?+eY?-w9XlN3P=%SXkZ1lSJ%y`%3543M0kp*cc8z1Z*hF@g&Z;U@|i zra%hlUGtLyDX{dg4%GYM1WrKW7=qjab0-TS*#M;dkT9oc7ILq@GuRkxdi%R*l-vCe z`;XB7A^LxC0&2<)1NUs;9-z>x@NIypTbc1n?>pEj+r-2uJ}HW$mM%boCWA zU8-ed$*;HM18tZ)2ea2xj-%;kd05WvdB^5(e`y1+dcN?)++U*0qsKP%{@&e7p!uJl z_}Zu_j_qr#Am@unYn5i{O0|4A!htMVPN1IQ;Ta){wQi|e*6TG2^CK#0XE!479FNcOaIXQ*EX2HcVy80w@n2MZX-24>hJU)_jkO5&7my%cT7bLemM96I=}Dj ze>m7cep4W(ecyVn|AD#uSQ>jOqZyiiipPZnNV5d>i*d~RYcGa*cd=_|6FK8JbHxVc zJOlUrGgr_S_RG^a_Ak`0@8Z~{4A{UdcA~cuncXsSZzM z6NQy_VJYnk@tq)(R%=8o$u2BVd2&%LH_O~ys|YnU6~9r08Lp%nF*%b95K1f0@quX7fRYlI)eOZEHK8~cB^%9hl}QyVzItA7q*vzG zlC4@WeA5_?i1iVa_u!LAF_4Cv7wyjI?S#m(B-7JcRk=;I>O`@|ws}Kp>3lBN)Y*EQ zAcj4k)|ZX47z;}2(p*((FACKdZ04eb(h%D6Ofo^uCL$8M9AQ&pzLu3Fqj}xOWQA2l zA#&~bs+wD^R*h6;zT*p*L?OpjZX{CKo5XZ$KEllPYML^mRR|xUrwzJ8tanpdsNK=H z#2gWw6{C?gBj2dR%N6Ekw5VpH>vc6zmf2jH2rn&%B0(l8E=H8=vjSUL@~xR|$}TPj z3hjkeGO|)mtu-ShISac|Vu_!wWV=DG(@>RpEoW##Ix@4GITthu{6|J^T6p070q)mjD0& delta 266 zcmZp8z}N7AZ-O+V_e2?IM(>RYOZb^JST9Xx7YN*}s8Gea`I4wUmm~uN11G;R1OIOR z=lo6lYxt-0U*h-XPv$q?ELgzH&%>Y0paN6^29u5TQ$&T?nRPi*vJ&&s^Wzg!ax?Sd zbMn(CuhCa!)YyDU-^76JIs<<;-*uo3r};P<1)2C68f_USAI(c@cFf=In9q3FVKd8u zKk~~I7zNPrHU&mTh3$+EOw0}(Ones^_}%#~0?jGnV>~lCQ6Omh#XcruMh+(a?F{^P w__qVyx0-+Y_W4Y{K%v77{Ezq#1BJHpv&b str: return f"{self.pk} {self.title}" #pk- primary key @@ -42,6 +50,10 @@ def delete(self, *args): self.deleted = True self.save() + class Meta: + verbose_name = ('Курс') + verbose_name_plural = ('Курсы') + class Lesson(models.Model): course = models.ForeignKey(Courses, on_delete=models.CASCADE) num = models.PositiveIntegerField(verbose_name="Lesson number") diff --git a/mainapp/templates/includes/head_menu.html b/mainapp/templates/includes/head_menu.html index 608b8e7..c74efbe 100644 --- a/mainapp/templates/includes/head_menu.html +++ b/mainapp/templates/includes/head_menu.html @@ -31,7 +31,11 @@ Мои курсы Модерация - Административный раздел + {% if user.is_supreuser %} + Административный + раздел + {% endif %} + Выход From fe933585937ff3aca6ab7bc5535373d2bc4938ba Mon Sep 17 00:00:00 2001 From: Gleb Sviridoff Date: Tue, 31 May 2022 06:46:03 +0300 Subject: [PATCH 08/20] Not work extption Unknown field(s) (avatar, age, email) --- authapp/forms.py | 72 +++++++++++------------ authapp/models.py | 13 ++-- authapp/views.py | 40 ++++++------- mainapp/templates/includes/head_menu.html | 7 ++- 4 files changed, 66 insertions(+), 66 deletions(-) diff --git a/authapp/forms.py b/authapp/forms.py index 2ddb225..7f5c4f5 100644 --- a/authapp/forms.py +++ b/authapp/forms.py @@ -1,59 +1,57 @@ -from django.contrib.auth.forms import UserCreationForm, UsernameField, UserChangeForm -from .models import CustomUser -from django.contrib.auth import get_user_model -from django.core.exceptions import ValidationError import os from django import forms +from django.contrib.auth import get_user_model +from django.contrib.auth.forms import UserCreationForm, UsernameField +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ - -class CustomUserChangeForm(forms.ModelForm): - class Meta: - model = get_user_model() - fields = ( +class CustomUserCreationForm(UserCreationForm): + field_order = [ "username", + "password1", + "password2", "email", "first_name", "last_name", "age", "avatar", - ) + ] + + class Meta: + model = get_user_model() + fields = ( + "username", + "email", + "first_name", + "last_name", + "age", + "avatar", + ) field_classes = {"username": UsernameField} + +class CustomUserChangeForm(forms.ModelForm): + class Meta: + model = get_user_model() + fields = ( + "username", + "email", + "first_name", + "last_name", + "age", + "avatar", + ) + field_classes = {"username": UsernameField} + def clean_avatar(self): arg_as_str = "avatar" if arg_as_str in self.changed_data and self.instance.avatar: if os.path.exists(self.instance.avatar.path): os.remove(self.instance.avatar.path) return self.cleaned_data.get(arg_as_str) - + def clean_age(self): data = self.cleaned_data.get("age") if data < 10 or data > 100: raise ValidationError(_("Please, enter a valid age!")) return data - - - -class CustomUserCreationForm(UserCreationForm): - field_order = [ - "username", - "password1", - "password2", - - "first_name", - "last_name", - "age", - "avatar", - ] - - class Meta: - model = get_user_model() - fields = ( - "username", - - "first_name", - "last_name", - "age", - "avatar", - ) - field_classes = {"username": UsernameField} diff --git a/authapp/models.py b/authapp/models.py index e38b62a..1021cab 100644 --- a/authapp/models.py +++ b/authapp/models.py @@ -21,13 +21,13 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): _("username"), max_length=150, unique=True, - help_text=_('150 символов и цифр включая @ . + - _' + help_text=_( + "Required. 150 characters or fewer. ASCII letters and digits only." ), - validators = [username_validator], - error_messages = { - 'unique': _('Пользователь с таким ником уже существует') - }, - ) + validators=[username_validator], + error_messages={ + "unique": _("A user with that username already exists."), + },) first_name = models.CharField(_('Имя'), max_length=150, blank=True) last_name = models.CharField(_('Фамилия'), max_length=150, blank=True) @@ -42,6 +42,7 @@ def clean(self): def get_full_name(self): full_name = "%s %s" % (self.first_name, self.last_name) return full_name.strip() + def get_short_name(self): """Return the short name for the user.""" return self.first_name diff --git a/authapp/views.py b/authapp/views.py index 2eccb70..80f728a 100644 --- a/authapp/views.py +++ b/authapp/views.py @@ -1,23 +1,18 @@ -from .forms import CustomUserCreationForm, CustomUserChangeForm -from django.shortcuts import render -from django.contrib.auth.views import LoginView, LogoutView -from django.views.generic.edit import CreateView from django.contrib import messages -from authapp import models -from django.views.generic import TemplateView -from django.contrib.auth.mixins import LoginRequiredMixin -from django.utils.safestring import mark_safe -from django.urls import reverse_lazy -import os -from django.http import HttpResponseRedirect from django.contrib.auth import get_user_model - - +from django.contrib.auth.mixins import UserPassesTestMixin +from django.contrib.auth.views import LoginView, LogoutView +from django.urls import reverse_lazy +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ +from django.views.generic import CreateView, UpdateView +from authapp import forms class CustomLoginView(LoginView): def form_valid(self, form): ret = super().form_valid(form) - message = ("Login success!
    Hi, %(username)s") % {"username": self.request.user.get_full_name() + message = _("Login success!
    Hi, %(username)s") % { + "username": self.request.user.get_full_name() if self.request.user.get_full_name() else self.request.user.get_username() } @@ -26,26 +21,31 @@ def form_valid(self, form): def form_invalid(self, form): for _unused, msg in form.error_messages.items(): - messages.add_message(self.request, messages.WARNING, mark_safe(f"Something goes worng:
    {msg}"),) + messages.add_message( + self.request, + messages.WARNING, + mark_safe(f"Something goes worng:
    {msg}"), + ) return self.render_to_response(self.get_context_data(form=form)) class CustomLogoutView(LogoutView): def dispatch(self, request, *args, **kwargs): - messages.add_message(self.request, messages.INFO, ("See you later!")) + messages.add_message(self.request, messages.INFO, _("See you later!")) return super().dispatch(request, *args, **kwargs) - class RegisterView(CreateView): model = get_user_model() - form_class = CustomUserCreationForm + form_class = forms.CustomUserCreationForm success_url = reverse_lazy("mainapp:main_page") class ProfileEditView(UserPassesTestMixin, UpdateView): model = get_user_model() - form_class = CustomUserChangeForm - + form_class = forms.CustomUserChangeForm def test_func(self): return True if self.request.user.pk == self.kwargs.get("pk") else False def get_success_url(self): return reverse_lazy("authapp:profile_edit", args=[self.request.user.pk]) + + +продолжить с 45 страници \ No newline at end of file diff --git a/mainapp/templates/includes/head_menu.html b/mainapp/templates/includes/head_menu.html index c74efbe..4cb995d 100644 --- a/mainapp/templates/includes/head_menu.html +++ b/mainapp/templates/includes/head_menu.html @@ -22,12 +22,13 @@ + {% if user.is_active %} + {% else %} + вход + {% endif %} diff --git a/mainapp/views.py b/mainapp/views.py index 5c3c6ee..ed35569 100644 --- a/mainapp/views.py +++ b/mainapp/views.py @@ -31,6 +31,7 @@ def get_context_data(self, pk=None, **kwargs): class NewsListView(ListView): + template_name = 'mainapp/news.html' model = mainapp_models.News paginate_by = 5 diff --git a/media/mark b/media/mark new file mode 100644 index 0000000..779590f --- /dev/null +++ b/media/mark @@ -0,0 +1,374 @@ +[usb] pm3 --> hw connect +[=] Using UART port /dev/ttyACM0 +[=] Communicating with PM3 over USB-CDC + + +метка №1 мобильный обходчик hf + +TYPE: NTAG 213 144bytes (NT2H1311G0DU) +[+] UID: 04 87 53 8A 50 5D 80 +[+] UID[0]: 04, NXP Semiconductors Germany +[+] BCC0: 58 ( ok ) +[+] BCC1: 07 ( ok ) +[+] Internal: 48 ( default ) +[+] Lock: 00 00 - 0000000000000000 +[+] OneTimePad: E1 10 12 00 - 11100001000100000001001000000000 + +[=] --- NDEF Message +[+] Capability Container: E1 10 12 00 +[+] E1: NDEF Magic Number +[+] 10: version 0.1 supported by tag +[+] : Read access granted without any security / Write access granted without any security +[+] 12: Physical Memory Size: 144 bytes +[+] 12: NDEF Memory Size: 144 bytes +[+] Additional feature information +[+] 00 +[+] 00000000 +[+] xxx - 00: RFU ( ok ) +[+] x - 00: don't support special frame +[+] x - 00: don't support lock block +[+] xx - 00: RFU ( ok ) +[+] x - 00: IC don't support multiple block reads + +[=] --- Tag Counter +[=] [02]: 00 00 00 +[+] - BD tearing ( ok ) + +[=] --- Tag Signature +[=] IC signature public key name: NXP NTAG21x (2013) +[=] IC signature public key value: 04494E1A386D3D3CFE3DC10E5DE68A499B1C202DB5B132393E89ED19FE5BE8BC61 +[=] Elliptic curve parameters: NID_secp128r1 +[=] TAG IC Signature: 883432C6DE522C788AE5EBCB287665289AD58719DF015922BCA993E95EA1E40A +[+] Signature verification ( successful ) + +[=] --- Tag Silicon Information +[=] Wafer Counter: 17541649 ( 0x10BAA11 ) +[=] Wafer Coordinates: x 135, y 83 (0x87, 0x53) +[=] Test Site: 2 + +[=] --- Tag Version +[=] Raw bytes: 00 04 04 02 01 00 0F 03 +[=] Vendor ID: 04, NXP Semiconductors Germany +[=] Product type: 04, NTAG +[=] Product subtype: 02, 50pF +[=] Major version: 01 +[=] Minor version: 00 +[=] Size: 0F, (256 <-> 128 bytes) +[=] Protocol type: 03, ISO14443-3 Compliant + +[=] --- Tag Configuration +[=] cfg0 [41/0x29]: 04 00 00 FF +[=] - strong modulation mode disabled +[=] - pages don't need authentication +[=] cfg1 [42/0x2A]: 00 05 00 00 +[=] - Unlimited password attempts +[=] - NFC counter disabled +[=] - NFC counter not protected +[=] - user configuration writeable +[=] - write access is protected with password +[=] - 05, Virtual Card Type Identifier is default +[=] PWD [43/0x2B]: 00 00 00 00 - (cannot be read) +[=] PACK [44/0x2C]: 00 00 - (cannot be read) +[=] RFU [44/0x2C]: 00 00 - (cannot be read) + +[+] --- Known EV1/NTAG passwords +[+] Found default password FF FF FF FF pack 00 00 +[=] ------------------------ Fingerprint ----------------------- +[=] Reading tag memory... +[=] ------------------------------------------------------------ + + + + +ключ от качалки №1 lf + +NOTE: some demods output possible binary +[=] if it finds something that looks like a tag +[=] False Positives ARE possible +[=] +[=] Checking for known tags... +[=] +[+] EM 410x ID 0300419CC4 +[+] EM410x ( RF/64 ) +[=] -------- Possible de-scramble patterns --------- +[+] Unique TAG ID : C000823923 +[=] HoneyWell IdentKey +[+] DEZ 8 : 04299972 +[+] DEZ 10 : 0004299972 +[+] DEZ 5.5 : 00065.40132 +[+] DEZ 3.5A : 003.40132 +[+] DEZ 3.5B : 000.40132 +[+] DEZ 3.5C : 065.40132 +[+] DEZ 14/IK2 : 00012889201860 +[+] DEZ 15/IK3 : 000824642255139 +[+] DEZ 20/ZK : 12000000080203090203 +[=] +[+] Other : 40132_065_04299972 +[+] Pattern Paxton : 55958212 [0x355DAC4] +[+] Pattern 1 : 8544597 [0x826155] +[+] Pattern Sebury : 40132 65 4299972 [0x9CC4 0x41 0x419CC4] +[=] ------------------------------------------------ + +[+] Valid EM410x ID found! + +[=] Couldn't identify a chipset + + + +ключ от качалки №2 lf (с надписью + +NOTE: some demods output possible binary +[=] if it finds something that looks like a tag +[=] False Positives ARE possible +[=] +[=] Checking for known tags... +[=] +[+] EM 410x ID 15000E041A +[+] EM410x ( RF/64 ) +[=] -------- Possible de-scramble patterns --------- +[+] Unique TAG ID : A800702058 +[=] HoneyWell IdentKey +[+] DEZ 8 : 00918554 +[+] DEZ 10 : 0000918554 +[+] DEZ 5.5 : 00014.01050 +[+] DEZ 3.5A : 021.01050 +[+] DEZ 3.5B : 000.01050 +[+] DEZ 3.5C : 014.01050 +[+] DEZ 14/IK2 : 00090195231770 +[+] DEZ 15/IK3 : 000721561854040 +[+] DEZ 20/ZK : 10080000070002000508 +[=] +[+] Other : 01050_014_00918554 +[+] Pattern Paxton : 354566682 [0x1522421A] +[+] Pattern 1 : 853036 [0xD042C] +[+] Pattern Sebury : 1050 14 918554 [0x41A 0xE 0xE041A] +[=] ------------------------------------------------ + +[+] Valid EM410x ID found! + + + + +Домофон + +[=] NOTE: some demods output possible binary +[=] if it finds something that looks like a tag +[=] False Positives ARE possible +[=] +[=] Checking for known tags... +[=] +[+] EM 410x ID 0A000DF94D +[+] EM410x ( RF/64 ) +[=] -------- Possible de-scramble patterns --------- +[+] Unique TAG ID : 5000B09FB2 +[=] HoneyWell IdentKey +[+] DEZ 8 : 00915789 +[+] DEZ 10 : 0000915789 +[+] DEZ 5.5 : 00013.63821 +[+] DEZ 3.5A : 010.63821 +[+] DEZ 3.5B : 000.63821 +[+] DEZ 3.5C : 013.63821 +[+] DEZ 14/IK2 : 00042950588749 +[+] DEZ 15/IK3 : 000343608958898 +[+] DEZ 20/ZK : 05000000110009151102 +[=] +[+] Other : 63821_013_00915789 +[+] Pattern Paxton : 170014541 [0xA22374D] +[+] Pattern 1 : 522066 [0x7F752] +[+] Pattern Sebury : 63821 13 915789 [0xF94D 0xD 0xDF94D] +[=] ------------------------------------------------ + +[+] Valid EM410x ID found! + + + + + + +Пропуск от работы старый + +[=] NOTE: some demods output possible binary +[=] if it finds something that looks like a tag +[=] False Positives ARE possible +[=] +[=] Checking for known tags... +[=] +[+] Indala (len 64) Raw: a0000000e8f91261 +[+] Fmt 26 FC: 177 Card: 46417 Parity: 10 +[+] Possible de-scramble patterns +[+] Printed | __0155__ [0x9B] +[+] Internal ID | 1761153633 +[+] Heden-2L | 45778 + +[+] Valid Indala ID found! + +[=] Couldn't identify a chipset + + +Новый проууск +a +Searching for ISO14443-A tag... +[+] UID: 64 E8 AB 4A +[+] ATQA: 00 04 +[+] SAK: 08 [2] +[+] Possible types: +[+] MIFARE Classic 1K +[=] proprietary non iso14443-4 card found, RATS not supported +[#] BCC0 incorrect, got 0x00, expected 0x01 +[#] Aborting +[#] Auth error +[?] Hint: try `hf mf` commands + + + +метка мобильного обходчика перечеркнутая +--- Tag Information -------------------------- +[=] ------------------------------------------------------------- +[+] TYPE: NTAG 213 144bytes (NT2H1311G0DU) +[+] UID: 04 EE 6A 8A 50 5D 80 +[+] UID[0]: 04, NXP Semiconductors Germany +[+] BCC0: 08 ( ok ) +[+] BCC1: 07 ( ok ) +[+] Internal: 48 ( default ) +[+] Lock: 00 00 - 0000000000000000 +[+] OneTimePad: E1 10 12 00 - 11100001000100000001001000000000 + +[=] --- NDEF Message +[+] Capability Container: E1 10 12 00 +[+] E1: NDEF Magic Number +[+] 10: version 0.1 supported by tag +[+] : Read access granted without any security / Write access granted without any security +[+] 12: Physical Memory Size: 144 bytes +[+] 12: NDEF Memory Size: 144 bytes +[+] Additional feature information +[+] 00 +[+] 00000000 +[+] xxx - 00: RFU ( ok ) +[+] x - 00: don't support special frame +[+] x - 00: don't support lock block +[+] xx - 00: RFU ( ok ) +[+] x - 00: IC don't support multiple block reads + +[=] --- Tag Counter +[=] [02]: 00 00 00 +[+] - BD tearing ( ok ) + +[=] --- Tag Signature +[=] IC signature public key name: NXP NTAG21x (2013) +[=] IC signature public key value: 04494E1A386D3D3CFE3DC10E5DE68A499B1C202DB5B132393E89ED19FE5BE8BC61 +[=] Elliptic curve parameters: NID_secp128r1 +[=] TAG IC Signature: 5E91CFD1A307F80A006E18463434213D4134F47CCBF8064F4B8305DF52220648 +[+] Signature verification ( successful ) + +[=] --- Tag Silicon Information +[=] Wafer Counter: 17541649 ( 0x10BAA11 ) +[=] Wafer Coordinates: x 238, y 106 (0xEE, 0x6A) +[=] Test Site: 2 + +[=] --- Tag Version +[=] Raw bytes: 00 04 04 02 01 00 0F 03 +[=] Vendor ID: 04, NXP Semiconductors Germany +[=] Product type: 04, NTAG +[=] Product subtype: 02, 50pF +[=] Major version: 01 +[=] Minor version: 00 +[=] Size: 0F, (256 <-> 128 bytes) +[=] Protocol type: 03, ISO14443-3 Compliant + +[=] --- Tag Configuration +[=] cfg0 [41/0x29]: 04 00 00 FF +[=] - strong modulation mode disabled +[=] - pages don't need authentication +[=] cfg1 [42/0x2A]: 00 05 00 00 +[=] - Unlimited password attempts +[=] - NFC counter disabled +[=] - NFC counter not protected +[=] - user configuration writeable +[=] - write access is protected with password +[=] - 05, Virtual Card Type Identifier is default +[=] PWD [43/0x2B]: 00 00 00 00 - (cannot be read) +[=] PACK [44/0x2C]: 00 00 - (cannot be read) +[=] RFU [44/0x2C]: 00 00 - (cannot be read) + +[+] --- Known EV1/NTAG passwords +[+] Found default password FF FF FF FF pack 00 00 +[=] ------------------------ Fingerprint ----------------------- +[=] Reading tag memory... +[=] ------------------------------------------------------------ + + + +не перечеркнута метка +--- Tag Information -------------------------- +[=] ------------------------------------------------------------- +[+] TYPE: NTAG 213 144bytes (NT2H1311G0DU) +[+] UID: 04 87 53 8A 50 5D 80 +[+] UID[0]: 04, NXP Semiconductors Germany +[+] BCC0: 58 ( ok ) +[+] BCC1: 07 ( ok ) +[+] Internal: 48 ( default ) +[+] Lock: 00 00 - 0000000000000000 +[+] OneTimePad: E1 10 12 00 - 11100001000100000001001000000000 + +[=] --- NDEF Message +[+] Capability Container: E1 10 12 00 +[+] E1: NDEF Magic Number +[+] 10: version 0.1 supported by tag +[+] : Read access granted without any security / Write access granted without any security +[+] 12: Physical Memory Size: 144 bytes +[+] 12: NDEF Memory Size: 144 bytes +[+] Additional feature information +[+] 00 +[+] 00000000 +[+] xxx - 00: RFU ( ok ) +[+] x - 00: don't support special frame +[+] x - 00: don't support lock block +[+] xx - 00: RFU ( ok ) +[+] x - 00: IC don't support multiple block reads + +[=] --- Tag Counter +[=] [02]: 00 00 00 +[+] - BD tearing ( ok ) + +[=] --- Tag Signature +[=] IC signature public key name: NXP NTAG21x (2013) +[=] IC signature public key value: 04494E1A386D3D3CFE3DC10E5DE68A499B1C202DB5B132393E89ED19FE5BE8BC61 +[=] Elliptic curve parameters: NID_secp128r1 +[=] TAG IC Signature: 883432C6DE522C788AE5EBCB287665289AD58719DF015922BCA993E95EA1E40A +[+] Signature verification ( successful ) + +[=] --- Tag Silicon Information +[=] Wafer Counter: 17541649 ( 0x10BAA11 ) +[=] Wafer Coordinates: x 135, y 83 (0x87, 0x53) +[=] Test Site: 2 + +[=] --- Tag Version +[=] Raw bytes: 00 04 04 02 01 00 0F 03 +[=] Vendor ID: 04, NXP Semiconductors Germany +[=] Product type: 04, NTAG +[=] Product subtype: 02, 50pF +[=] Major version: 01 +[=] Minor version: 00 +[=] Size: 0F, (256 <-> 128 bytes) +[=] Protocol type: 03, ISO14443-3 Compliant + +[=] --- Tag Configuration +[=] cfg0 [41/0x29]: 04 00 00 FF +[=] - strong modulation mode disabled +[=] - pages don't need authentication +[=] cfg1 [42/0x2A]: 00 05 00 00 +[=] - Unlimited password attempts +[=] - NFC counter disabled +[=] - NFC counter not protected +[=] - user configuration writeable +[=] - write access is protected with password +[=] - 05, Virtual Card Type Identifier is default +[=] PWD [43/0x2B]: 00 00 00 00 - (cannot be read) +[=] PACK [44/0x2C]: 00 00 - (cannot be read) +[=] RFU [44/0x2C]: 00 00 - (cannot be read) + +[+] --- Known EV1/NTAG passwords +[+] Found default password FF FF FF FF pack 00 00 +[=] ------------------------ Fingerprint ----------------------- +[=] Reading tag memory... +[=] ------------------------------------------------------------ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6df90f3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,31 @@ +asgiref==3.5.2 +beautifulsoup4==4.11.1 +bleach==4.1.0 +certifi==2022.5.18.1 +cffi==1.15.0 +charset-normalizer==2.0.12 +cryptography==37.0.2 +defusedxml==0.7.1 +Django==4.0.4 +django-crispy-forms==1.14.0 +django-markdownify==0.9.1 +idna==3.3 +importlib-metadata==4.11.4 +Markdown==3.3.7 +markdownify==0.11.2 +oauthlib==3.2.0 +packaging==21.3 +pycparser==2.21 +PyJWT==2.4.0 +pyparsing==3.0.9 +python3-openid==3.2.0 +requests==2.27.1 +requests-oauthlib==1.3.1 +six==1.16.0 +social-auth-app-django==5.0.0 +social-auth-core==4.2.0 +soupsieve==2.3.2.post1 +sqlparse==0.4.2 +urllib3==1.26.9 +webencodings==0.5.1 +zipp==3.8.0 From 6afd204559d2ee4891b5e1f8182cfa97a769b396 Mon Sep 17 00:00:00 2001 From: Gleb Sviridoff Date: Wed, 1 Jun 2022 11:02:40 +0300 Subject: [PATCH 14/20] lesson_6 end --- ...er_age_alter_customuser_avatar_and_more.py | 28 ++++++ .../migrations/0003_alter_customuser_age.py | 18 ++++ .../migrations/0004_alter_customuser_email.py | 18 ++++ authapp/models.py | 54 +++++------ db.sqlite3 | Bin 253952 -> 266240 bytes mainapp/models.py | 14 ++- mainapp/templates/mainapp/courses_detail.html | 2 +- mainapp/templates/mainapp/news_form.html | 4 +- mainapp/templates/mainapp/news_list.html | 85 ++++++++++++++++++ mainapp/views.py | 13 ++- 10 files changed, 184 insertions(+), 52 deletions(-) create mode 100644 authapp/migrations/0002_alter_customuser_age_alter_customuser_avatar_and_more.py create mode 100644 authapp/migrations/0003_alter_customuser_age.py create mode 100644 authapp/migrations/0004_alter_customuser_email.py create mode 100644 mainapp/templates/mainapp/news_list.html diff --git a/authapp/migrations/0002_alter_customuser_age_alter_customuser_avatar_and_more.py b/authapp/migrations/0002_alter_customuser_age_alter_customuser_avatar_and_more.py new file mode 100644 index 0000000..f2c5205 --- /dev/null +++ b/authapp/migrations/0002_alter_customuser_age_alter_customuser_avatar_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.0.4 on 2022-06-01 06:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authapp', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='age', + field=models.SmallIntegerField(blank=True, verbose_name='age'), + ), + migrations.AlterField( + model_name='customuser', + name='avatar', + field=models.FileField(blank=True, upload_to='', verbose_name='avatar'), + ), + migrations.AlterField( + model_name='customuser', + name='email', + field=models.EmailField(blank=True, max_length=254, unique=True, verbose_name='email'), + ), + ] diff --git a/authapp/migrations/0003_alter_customuser_age.py b/authapp/migrations/0003_alter_customuser_age.py new file mode 100644 index 0000000..83ea6ce --- /dev/null +++ b/authapp/migrations/0003_alter_customuser_age.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2022-06-01 06:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authapp', '0002_alter_customuser_age_alter_customuser_avatar_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='age', + field=models.SmallIntegerField(blank=True, null=True, verbose_name='age'), + ), + ] diff --git a/authapp/migrations/0004_alter_customuser_email.py b/authapp/migrations/0004_alter_customuser_email.py new file mode 100644 index 0000000..7d292ad --- /dev/null +++ b/authapp/migrations/0004_alter_customuser_email.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2022-06-01 06:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authapp', '0003_alter_customuser_age'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='email', + field=models.EmailField(blank=True, max_length=254, verbose_name='email'), + ), + ] diff --git a/authapp/models.py b/authapp/models.py index 8389d19..273b5d6 100644 --- a/authapp/models.py +++ b/authapp/models.py @@ -14,38 +14,27 @@ def users_avatars_path(instance, filename): suff = Path(filename).suffix return f"user_{instance.user_name}/avatars/pic_{num}{suff}" +class UserManager(BaseUserManager): + def create_user(self, user_name, password=None): + if not user_name: + raise ValueError('Должно быть задано имя пользователя') -class CustomAccountManager(BaseUserManager): + user = self.model(user_name=user_name) - def create_user(self, position, email, user_name, password, **other_fields): - - if not email: - raise ValueError(_('Email должен быть для создания пользователя')) - elif not password: - raise ValueError(_('необходимо задать пароль')) - elif not user_name: - raise ValueError(_('невозможно создать пользователя без имени')) - - if position == 'staff': - other_fields.setdefault('is_staff', True) - position = 'админ без доступа к новостям' - elif position == 'superuser': - other_fields.setdefault('is_staff', True) - other_fields.setdefault('is_superuser', True) - position = 'суперпользователь' - elif position == 'news_manadjer': - other_fields.setdefault('is_staff', True) - other_fields.setdefault('is_news_manadjer', True) - position = 'менеджер новостей' - else: - position = 'обычный пользователь' - messages.add_message(self.request, messages.INFO, _(f"создан {position} \n{self.request.user.get_full_name()}")) - - user = self.model(email=email, user_name=user_name, **other_fields) user.set_password(password) user.save() + return user + + def create_superuser(self, user_name, password=None): + user = self.create_user( + user_name, + password=password, + ) - return self.create_user(email, user_name, password, **other_fields) + user.is_admin = True + user.is_superuser = True + user.save() + return user @@ -58,10 +47,10 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): validators=[username_validator], error_messages={ "unique": _("Пользователь с таким ником уже существует"),},) first_name = models.CharField(_("first_name"), max_length=150, blank=True) last_name = models.CharField(_("last_name"), max_length=150, blank=True) - email = models.EmailField(_('email'), unique=True) + email = models.EmailField(_('email'), blank=True) mobile = models.CharField(_("mobile"), max_length=20, blank=True) - age = models.SmallIntegerField(_("age")) - avatar = models.FileField(_("avatar")) + age = models.SmallIntegerField(_("age"), blank=True, null=True) + avatar = models.FileField(_("avatar"), blank=True) is_staff = models.BooleanField(default=False) is_superuser = models.BooleanField(default=False) @@ -73,7 +62,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): USERNAME_FIELD = 'user_name' - objects = CustomAccountManager() + objects = UserManager() class Meta: @@ -94,4 +83,5 @@ def get_short_name(self): def send_email_user(self, subject, message, from_email=None, **kwargs): send_mail(subject, message, from_email, [self.email], **kwargs) - + def __str__(self): + return self.user_name diff --git a/db.sqlite3 b/db.sqlite3 index 63cab7e4aa5ede19d14422074d92215ad5adef5a..4631abd1763871011dffbd24473e44b715e470d8 100644 GIT binary patch delta 1623 zcmah}O>7fK6yDic+p!aSZ6FDe3rUbGASw2E|Lk=eh@Ci2oY*l5A#pBYN))MzdMGzqwW@@Q+FLF~?bt-Bp+TLQH=21* z^X)g^zIm1VbmcSShh~$PAP6@$6E+Il<(;=!;Jth4WBO14Z~JU`I%3x2c|^t6G{$+g z>TA`Oif>59$5h!pq9V)_ZmFasq)j(gx}p9$K*z8)PasA_`k6EM=)wK2$`>ZuU4-SC0_q5fn~1)tCMl&cn%iKvd5 z^?E&_@50UP*5}t#a*SCkCM1RzEG&cdMV6TqilSmJJ?Rf#kOSfQ>7rwn5iPcSekIAz ztu9Zb(@5A%ISPxB3$ew8%<_eB$?n`J&CIM$XER&!Mdz3#$(dx9>6EnWa3SR49adHp zc}DCEbw&h7b(m`L?0vyg`+A<<4Y|ZAiHP8FE@xwb{BlG_5sxJmSPBX={7R%yl)b#X z%`C5{&nw09rZ1MBiHGGv*b@@m3n(*^h$(t*?B8+I(Vm0 zJqueT2M!+|0Q+K|K2S_05DF!n%H(=^DYhPpJHvj5dz-&(32sD2^AW)oob++x`Kg>I zzLmeSJ-b?rFL5b;#h*r7)2X1}H|KQkt0~9gc(7cnpLm%WBf1#gD)LS{;$Igjb4&{% zE|1IcSi)M8swc?S75e{I=f#Cwa5}KQ9GZ%IF<;uhnMj7?o7;!^ZU@Qc?H~-BM!JEYP#;!pYx=35sCRUK>xQ*4cpdoFyTq+_$2V`cTNvmH zrT&y?Ek4&>9lxaMx7)${qs=EQI0WiPd0?&kdGNUY2L}eh>E`taG(xItiQw@8J2@fM zgDkMsAF$v^vp){c0;;D5ObyAX2hCU#o@r?VGkBkbH}9aEcZ_YRo0Jj!HP{%cJfZ)c JYI)5dHK2W;eTL^EaBf+8C06B`wwJDg>&nBx+l1LbPINEtEFKMr~~kYI^X} zD5AXx>Za^L3V|N&p|Yj_P$-_fc(c$;PdP|I2zsg~vH#M7beMTC!@M_die;_1OV|CL zZiG+|ufLJsXz7IRou}_;Z5N-_w{r0MRgMv{$&|D~I~>|`*iH3EvNwq6SNe-a=|^hO zC7PgF8nnZC2i1L)7;>gCKbgtrpJWSj^D{3CbI)eYXxkPHQ) zb6wz)$A}*D*&+34BqCp6e$fCz*Eq8lO(WXuK5&a_cg>Mdd{FJVC){FYx@V^TJyAy?Pjl_j_Eolfk1%&X5oza#s z+Z~4f>J=65BX!GlS@t=Xr73aQ@!3Iyhh$9f<9+wGORagVe1}<7GBesFE5-0Cn0f6J z+waAS6T>iq0taB_#x=;~rD(6aA&XG0F zDqul;4Отзывы {% endfor %} {% else %}

    - No feedback yet. Be first! + Нет отзывов. Будь первым!

    {% endif %} diff --git a/mainapp/templates/mainapp/news_form.html b/mainapp/templates/mainapp/news_form.html index 68e49a8..8893652 100644 --- a/mainapp/templates/mainapp/news_form.html +++ b/mainapp/templates/mainapp/news_form.html @@ -1,14 +1,12 @@ {% extends 'base.html' %} {% load crispy_forms_tags %} -mainapp/templates/mainapp/news_form.html {% block content %}
    {% csrf_token %} {{ form|crispy }} - +
    diff --git a/mainapp/templates/mainapp/news_list.html b/mainapp/templates/mainapp/news_list.html new file mode 100644 index 0000000..4650757 --- /dev/null +++ b/mainapp/templates/mainapp/news_list.html @@ -0,0 +1,85 @@ +{% extends 'base.html' %} +{% block title %} +Новости +{% endblock title %} +{% block content %} +{% if page_num %} +
    +
    +

    Текущая страница: {{ page_num }}

    +
    +
    +{% endif %} +
    +
    + {% for item in object_list %} +
    +
    +
    {{ item.title }}
    +
    + {{ item.created|date:"Y-m-d h-i-s" }} +
    +

    {{ item.preambule }}

    +
    + + {% if perms.mainapp.change_news %} +
    + + + +
    + {% endif %} + {% if perms.mainapp.delete_news %} +
    + + + +
    + {% endif %} +
    +
    +
    + {% endfor %} +
    +
    +
    +
    + Фильтры +
    +
    +
    +
    +
    + Дата от... +
    + +
    +
    +
    + Дата до... +
    + +
    + +
    +
    +
    + {% if perms.mainapp.add_news %} + Добавить новость + {% endif %} +
    +
    +
    + +
    +{% endblock content %} \ No newline at end of file diff --git a/mainapp/views.py b/mainapp/views.py index ed35569..6ea0c66 100644 --- a/mainapp/views.py +++ b/mainapp/views.py @@ -40,9 +40,9 @@ def get_queryset(self): class NewsCreateView(PermissionRequiredMixin, CreateView): model = mainapp_models.News - fields = "__all__" + fields = "__all__" # использование всех полей success_url = reverse_lazy("mainapp:news") - permission_required = ("mainapp.add_news",) + permission_required = ("mainapp.add_news",) # PermissionRequiredMixin проверять, предоставляются ли пользователю права для создания новостей class NewsDetailView(DetailView): model = mainapp_models.News @@ -72,10 +72,8 @@ class CoursesDetailView(TemplateView): def get_context_data(self, pk=None, **kwargs): context = super(CoursesDetailView, self).get_context_data(**kwargs) - context["course_object"] = get_object_or_404( - mainapp_models.Courses, pk=pk) - context["lessons"] = mainapp_models.Lesson.objects.filter( - course=context["course_object"]) + context["course_object"] = get_object_or_404(mainapp_models.Courses, pk=pk) + context["lessons"] = mainapp_models.Lesson.objects.filter(course=context["course_object"]) context["teachers"] = mainapp_models.CourseTeachers.objects.filter( course=context["course_object"]) if not self.request.user.is_anonymous: @@ -83,8 +81,7 @@ def get_context_data(self, pk=None, **kwargs): course=context["course_object"], user=self.request.user).count(): context["feedback_form"] = mainapp_forms.CourseFeedbackForm(course=context["course_object"], user=self.request.user) context["feedback_list"] = mainapp_models.CourseFeedback.objects.filter( - course=context["course_object"] - ).order_by("-created", "-rating")[:5] + course=context["course_object"]).order_by("-created", "-rating")[:5] return context class CourseFeedbackFormProcessView(LoginRequiredMixin, CreateView): From 1b3c5332c9bde18e1080ef605f671d7be2197a76 Mon Sep 17 00:00:00 2001 From: Gleb Sviridoff Date: Wed, 1 Jun 2022 13:52:20 +0300 Subject: [PATCH 15/20] page 34, 50% lesson_7 --- config/settings.py | 28 ++++++++++++++++++- config/urls.py | 2 ++ db.sqlite3 | Bin 266240 -> 266240 bytes mainapp/templates/includes/head_menu.html | 1 + mainapp/templates/mainapp/log_view.html | 13 +++++++++ mainapp/urls.py | 2 ++ mainapp/views.py | 28 +++++++++++++++++-- requirments.txt | 32 ++++++++++++++++++++++ 8 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 mainapp/templates/mainapp/log_view.html create mode 100644 requirments.txt diff --git a/config/settings.py b/config/settings.py index 6af54fe..c7dc9b5 100644 --- a/config/settings.py +++ b/config/settings.py @@ -14,6 +14,9 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True +if DEBUG: + INTERNAL_IPS = ["127.0.0.1",] + ALLOWED_HOSTS = [] @@ -70,6 +73,14 @@ }, ] +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:6379", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + },}} + WSGI_APPLICATION = 'config.wsgi.application' @@ -142,4 +153,19 @@ SOCIAL_AUTH_GITHUB_KEY = '8b4874c2b4c54df66fe3' SOCIAL_AUTH_GITHUB_SECRET = os.environ.get('django_git_password') AUTH_USER_MODEL = 'authapp.CustomUser' -CRISPY_TEMPLATE_PACK = "bootstrap4" \ No newline at end of file +CRISPY_TEMPLATE_PACK = "bootstrap4" +LOG_FILE = BASE_DIR / "var" / "log" / "main_log.log" + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "console": { + "format": "[%(asctime)s] %(levelname)s %(name)s (%(lineno)d)%(message)s"}, + }, + "handlers": { + "console": {"class": "logging.StreamHandler", "formatter": "console"}, }, + "loggers": {"django": {"level": "INFO", "handlers": ["console"]}, + }, +} + diff --git a/config/urls.py b/config/urls.py index 939e903..f7aca26 100644 --- a/config/urls.py +++ b/config/urls.py @@ -14,4 +14,6 @@ ] if settings.DEBUG: + import debug_toolbar + urlpatterns.append(path("__debug__/", include(debug_toolbar.urls))) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 4631abd1763871011dffbd24473e44b715e470d8..e696868c1b1bbb63da5f4d98d5fd9b33ef6b3698 100644 GIT binary patch delta 304 zcmZozAkeTtV1hKG*F+g-Rxbv<;Dn7S3*vd0_$nCq4f!f~8h9Eu3nUcqurM$%ZJgYa zvlztSnyj6>0LWlWn|wZZWqqR=lORK*u&BJaJfkQFCkKb4qo!e!Nt!{brG=@XrMaP{ zrA4Z_v5|qfg{g^2vZYy)v01Wlih*HrYD$`Ua$=&fv5~QSc6w5FRz_%EZhD!ynVG4%xv80nNJdFox{-mAk*PAmtT{Cfk7)VF(o%M&xjSs mmIK)Y^bUh4M39+*gISajB-s3|zWrN0BM>uf|5ne;H30y$)>i8P delta 310 zcmZozAkeTtV1hKG>qHr6R#yhSaIcLi3*vcLc^Vk_4f%R_PxGp87Dy=IVQExh6`b6X zvzVn(kwtK_cJ2a}MmuJ~$>(!d%2=9O7@8QRq@|~%r#Pgfr)B7sCN?TD2{JU=ici+h zRjdyV%J=i|O$rZiOEoSm@XdEKcXuktP;pDucPoji$}b9bO${yA&bIV-kMK73OG|Ul z@hZ&7j7%@A3NCRCD6sS^woOdQ&CD}0Ff!6LFw-?KR4_2JGBUO@GSIWIG%+wW!6svA zXlia@+~~tN*(Co04=ev!2L4CАдминистративный раздел + Лог {% endif %} diff --git a/mainapp/templates/mainapp/log_view.html b/mainapp/templates/mainapp/log_view.html new file mode 100644 index 0000000..f337b5c --- /dev/null +++ b/mainapp/templates/mainapp/log_view.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} +mainapp/templates/mainapp/log_view.html +{% block content %} + +

    +

    Первые 1000 строк файла
    +
    {{ log }}
    +

    +{% endblock content %} \ No newline at end of file diff --git a/mainapp/urls.py b/mainapp/urls.py index 3414c1e..50a994f 100644 --- a/mainapp/urls.py +++ b/mainapp/urls.py @@ -16,4 +16,6 @@ path("course_feedback/",views.CourseFeedbackFormProcessView.as_view(),name="course_feedback",), path("contacts/", views.ContactsPageView.as_view(), name="contacts"), path("doc_site/", views.DocSitePageView.as_view(), name="doc_site"), + path("log_view/", views.LogView.as_view(), name="log_view"), + path("log_download/", views.LogDownloadView.as_view(), name="log_download"), ] \ No newline at end of file diff --git a/mainapp/views.py b/mainapp/views.py index 6ea0c66..0269cb3 100644 --- a/mainapp/views.py +++ b/mainapp/views.py @@ -1,12 +1,15 @@ from mainapp import models as mainapp_models from django.shortcuts import get_object_or_404 -from django.views.generic import TemplateView, ListView, CreateView, DetailView, DeleteView +from django.views.generic import TemplateView, View, ListView, CreateView, DetailView, DeleteView from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin from django.template.loader import render_to_string from django.urls import reverse_lazy from mainapp import forms as mainapp_forms from django.http import JsonResponse from django.views.generic import CreateView, UpdateView +from django.contrib.auth.mixins import UserPassesTestMixin +from django.http import FileResponse, JsonResponse +from config import settings class MainPageView(TemplateView): @@ -98,4 +101,25 @@ class ContactsPageView(TemplateView): template_name = "mainapp/contacts.html" class DocSitePageView(TemplateView): - template_name = "mainapp/doc_site.html" \ No newline at end of file + template_name = "mainapp/doc_site.html" + +class LogView(TemplateView): + template_name = "mainapp/log_view.html" + + def get_context_data(self, **kwargs): + context = super(LogView, self).get_context_data(**kwargs) + log_slice = [] + with open(settings.LOG_FILE, "r") as log_file: + for i, line in enumerate(log_file): + if i == 1000: + break + log_slice.insert(0, line) + context["log"] = "".join(log_slice) + return context + +class LogDownloadView(UserPassesTestMixin, View): + def test_func(self): + return self.request.user.is_superuser + + def get(self, *args, **kwargs): + return FileResponse(open(settings.LOG_FILE, "rb")) \ No newline at end of file diff --git a/requirments.txt b/requirments.txt new file mode 100644 index 0000000..1acba77 --- /dev/null +++ b/requirments.txt @@ -0,0 +1,32 @@ +asgiref==3.5.2 +beautifulsoup4==4.11.1 +bleach==4.1.0 +certifi==2022.5.18.1 +cffi==1.15.0 +charset-normalizer==2.0.12 +cryptography==37.0.2 +defusedxml==0.7.1 +Django==4.0.4 +django-crispy-forms==1.14.0 +django-debug-toolbar==3.4.0 +django-markdownify==0.9.1 +idna==3.3 +importlib-metadata==4.11.4 +Markdown==3.3.7 +markdownify==0.11.2 +oauthlib==3.2.0 +packaging==21.3 +pycparser==2.21 +PyJWT==2.4.0 +pyparsing==3.0.9 +python3-openid==3.2.0 +requests==2.27.1 +requests-oauthlib==1.3.1 +six==1.16.0 +social-auth-app-django==5.0.0 +social-auth-core==4.2.0 +soupsieve==2.3.2.post1 +sqlparse==0.4.2 +urllib3==1.26.9 +webencodings==0.5.1 +zipp==3.8.0 From 8e561ed84a84d640db640aa74894a96f59662f67 Mon Sep 17 00:00:00 2001 From: Gleb Sviridoff Date: Thu, 2 Jun 2022 10:36:51 +0300 Subject: [PATCH 16/20] lesson_7 finish, pre check --- config/__init__.py | 3 + config/celery.py | 7 ++ config/settings.py | 12 +++ mainapp/forms.py | 11 +- mainapp/tasks.py | 17 +++ mainapp/templates/mainapp/contacts.html | 13 ++- mainapp/templates/mainapp/courses_detail.html | 100 ++++++++++++++++-- mainapp/urls.py | 7 +- mainapp/views.py | 35 ++++++ 9 files changed, 191 insertions(+), 14 deletions(-) create mode 100644 config/celery.py create mode 100644 mainapp/tasks.py diff --git a/config/__init__.py b/config/__init__.py index e69de29..a55740a 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -0,0 +1,3 @@ +from .celery import celery_app + +__all__ = ("celery_app",) \ No newline at end of file diff --git a/config/celery.py b/config/celery.py new file mode 100644 index 0000000..9b3b422 --- /dev/null +++ b/config/celery.py @@ -0,0 +1,7 @@ +import os +from celery import Celery + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") +celery_app = Celery("braniac") +celery_app.config_from_object("django.conf:settings", namespace="CELERY") +celery_app.autodiscover_tasks() \ No newline at end of file diff --git a/config/settings.py b/config/settings.py index c7dc9b5..ea84b81 100644 --- a/config/settings.py +++ b/config/settings.py @@ -155,6 +155,8 @@ AUTH_USER_MODEL = 'authapp.CustomUser' CRISPY_TEMPLATE_PACK = "bootstrap4" LOG_FILE = BASE_DIR / "var" / "log" / "main_log.log" +CELERY_BROKER_URL = "redis://localhost:6379" +CELERY_RESULT_BACKEND = "redis://localhost:6379" LOGGING = { "version": 1, @@ -168,4 +170,14 @@ "loggers": {"django": {"level": "INFO", "handlers": ["console"]}, }, } +# глобально +EMAIL_HOST = "localhost" #'smtp.yandex.ru' +EMAIL_PORT = "465" # 465- mail, yandex +EMAIL_HOST_USER = os.environ.get('email') #"django@geekshop.local" # myname@yandex.ru +EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') # +EMAIL_USE_SSL = False # yandex True # google False +EMAIL_USE_TLS = True # google True , yandex - False +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" + +EMAIL_FILE_PATH = os.path.join(BASE_DIR, 'email_messages') \ No newline at end of file diff --git a/mainapp/forms.py b/mainapp/forms.py index 3998329..410fdee 100644 --- a/mainapp/forms.py +++ b/mainapp/forms.py @@ -18,4 +18,13 @@ class Meta: "course": forms.HiddenInput(), "user": forms.HiddenInput(), "rating": forms.RadioSelect(), - } \ No newline at end of file + } + +class MailFeedbackForm(forms.Form): + user_id = forms.IntegerField(widget=forms.HiddenInput) + message = forms.CharField(widget=forms.Textarea, help_text=_("Enter your message"), label=_("Message"),) + + def __init__(self, *args, user=None, **kwargs): + super().__init__(*args, **kwargs) + if user: + self.fields["user_id"].initial = user.pk \ No newline at end of file diff --git a/mainapp/tasks.py b/mainapp/tasks.py new file mode 100644 index 0000000..30533aa --- /dev/null +++ b/mainapp/tasks.py @@ -0,0 +1,17 @@ +from email import message_from_string +from django.core.mail import send_mail +from celery import shared_task + +from django.contrib.auth import get_user_model + +@shared_task +def send_feedback_to_email(message_from, body): + model_user = get_user_model() + if not model_user: + model_user = "Аноним" + + send_mail(f'Feedback from {message_from}', #subject + message=body, + recipient_list=['gas53@bk.ru'], + from_email='gas53@bk.ru', + fail_silently=False) \ No newline at end of file diff --git a/mainapp/templates/mainapp/contacts.html b/mainapp/templates/mainapp/contacts.html index abd1b4c..3f3c0b4 100644 --- a/mainapp/templates/mainapp/contacts.html +++ b/mainapp/templates/mainapp/contacts.html @@ -1,7 +1,7 @@ {% extends 'includes/base.html' %} {% load static %} {% load email_to_link %} - +{% load email_to_link crispy_forms_tags %} {% block content %} @@ -118,5 +118,14 @@
    Москва
    - +{% if form %} +

    +

    Отправить сообщение в техподдержку

    +
    + {% csrf_token %} + {{ form|crispy }} + +
    +

    +{% endif %} {% endblock content %} \ No newline at end of file diff --git a/mainapp/templates/mainapp/courses_detail.html b/mainapp/templates/mainapp/courses_detail.html index fd0b258..acfc555 100644 --- a/mainapp/templates/mainapp/courses_detail.html +++ b/mainapp/templates/mainapp/courses_detail.html @@ -1,9 +1,91 @@ -
    - {% if course_object.description_as_markdown %} - {{ course_object.description|markdownify }} - {% else %} - {{ course_object.description }} - {% endif %} +{% extends 'base.html' %} +{% load static markdownify crispy_forms_tags cache %} +{% block title %} +{{ course_object.name }} +{% endblock title %} +{% block content %} +

    + {{ course_object.name }} +

    +
    +
    +
    +
    + {% if course_object.description_as_markdown %} + {{ course_object.description|markdownify }} + {% else %} + {{ course_object.description }} + {% endif %} +
    +
    +

    + Преподаватели
    + {% for item in teachers %} + {{ item.name_second }} {{ item.name_first }}{% if forloop.last %}{% + else %},{% endif %} + {% endfor %} +

    +
    +
    +
    +
    +
    +
    + +
    +
    +

    + цена
    + + {{ course_object.cost}} +
    + Купить +

    +
    +
    +
    +
    +

    + цена
    + + {{ course_object.cost}} +
    + Купить +

    +
    +
    +
    + {% cache 300 lessons %} + {% for item in lessons %} +
    +
    +

    + +

    +
    +
    +
    +
    +
    + +
    +
    + {% if item.description_as_markdown %} + {{ item.description|markdownify }} + {% else %} + {{ item.description }} + {% endif %} +
    +
    +
    +
    +
    + {% endfor %} + {% endcache %}

    Отзывы

    @@ -13,7 +95,7 @@

    Отзывы

    {% endfor %} {% else %}

    - Нет отзывов. Будь первым! + No feedback yet. Be first!

    {% endif %}
    @@ -21,8 +103,7 @@

    Отзывы

    Добавить отзыв
    -
    Вы ещё не оставляли отзыв к этому - курсу
    +
    Вы ещё не оставляли отзыв ккурсу

    {% csrf_token %} @@ -33,6 +114,7 @@
    Вы ещё не оставляли о
    {% endif %} +{% endblock content %} {% block js %}