locadm 1 rok pred
commit
f898a28d26
39 zmenil súbory, kde vykonal 911 pridanie a 0 odobranie
  1. 8 0
      .idea/.gitignore
  2. 6 0
      .idea/inspectionProfiles/profiles_settings.xml
  3. 7 0
      .idea/misc.xml
  4. 8 0
      .idea/modules.xml
  5. 10 0
      .idea/pythonProject.iml
  6. 6 0
      .idea/vcs.xml
  7. 0 0
      examen/exam/catalog/__init__.py
  8. 12 0
      examen/exam/catalog/admin.py
  9. 6 0
      examen/exam/catalog/apps.py
  10. 70 0
      examen/exam/catalog/forms.py
  11. 191 0
      examen/exam/catalog/migrations/0001_initial.py
  12. 0 0
      examen/exam/catalog/migrations/__init__.py
  13. 38 0
      examen/exam/catalog/models.py
  14. 5 0
      examen/exam/catalog/static/css/styles.css
  15. 18 0
      examen/exam/catalog/templates/accounts/profile.html
  16. 42 0
      examen/exam/catalog/templates/base_generic.html
  17. 17 0
      examen/exam/catalog/templates/catalog/product_detail.html
  18. 19 0
      examen/exam/catalog/templates/catalog/product_list.html
  19. 13 0
      examen/exam/catalog/templates/index.html
  20. 10 0
      examen/exam/catalog/templates/profile.html
  21. 5 0
      examen/exam/catalog/templates/registration/logged_out.html
  22. 27 0
      examen/exam/catalog/templates/registration/login.html
  23. 6 0
      examen/exam/catalog/templates/registration/password_reset_complete.html
  24. 36 0
      examen/exam/catalog/templates/registration/password_reset_confirm.html
  25. 8 0
      examen/exam/catalog/templates/registration/password_reset_done.htm
  26. 2 0
      examen/exam/catalog/templates/registration/password_reset_email.html
  27. 9 0
      examen/exam/catalog/templates/registration/password_reset_form.html
  28. 34 0
      examen/exam/catalog/templates/registration/registration.html
  29. 3 0
      examen/exam/catalog/tests.py
  30. 22 0
      examen/exam/catalog/urls.py
  31. 6 0
      examen/exam/catalog/validators.py
  32. 54 0
      examen/exam/catalog/views.py
  33. BIN
      examen/exam/db.sqlite3
  34. 0 0
      examen/exam/exam/__init__.py
  35. 16 0
      examen/exam/exam/asgi.py
  36. 129 0
      examen/exam/exam/settings.py
  37. 30 0
      examen/exam/exam/urls.py
  38. 16 0
      examen/exam/exam/wsgi.py
  39. 22 0
      examen/exam/manage.py

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Black">
+    <option name="sdkName" value="Python 3.12 (pythonProject)" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (pythonProject)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/pythonProject.iml" filepath="$PROJECT_DIR$/.idea/pythonProject.iml" />
+    </modules>
+  </component>
+</project>

+ 10 - 0
.idea/pythonProject.iml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.venv" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 0 - 0
examen/exam/catalog/__init__.py


+ 12 - 0
examen/exam/catalog/admin.py

@@ -0,0 +1,12 @@
+from django.contrib import admin
+from .models import Manufacturer, Product
+
+class ManufacturerAdmin(admin.ModelAdmin):
+    list_display = ('name',)
+
+# Register the admin class with the associated model
+admin.site.register(Manufacturer, ManufacturerAdmin)
+
+@admin.register(Product)
+class ProductAdmin(admin.ModelAdmin):
+    pass

+ 6 - 0
examen/exam/catalog/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class CatalogConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "catalog"

+ 70 - 0
examen/exam/catalog/forms.py

@@ -0,0 +1,70 @@
+from .models import User
+from django.core.exceptions import ValidationError
+from django.core.validators import RegexValidator
+from django import forms
+
+
+from .validators import validate_pasword_len
+
+
+class RegisterUserForm(forms.ModelForm):
+    first_name = forms.CharField(
+        label='Имя',
+        validators=[RegexValidator('^[а-яА-Я- -]+$',
+                                   message="Разрешены только кириллица, дефис и пробелы")],
+        error_messages={'required': 'Обязательное поле', }
+    )
+    last_name = forms.CharField(
+        label='Фамилия',
+        validators=[RegexValidator('^[а-яА-Я- -]+$',
+                                   message="Разрешены только кириллица, дефис и пробелы")],
+        error_messages={'required': 'Обязательное поле', }
+    )
+    username = forms.CharField(label='Логин',
+                                validators=[RegexValidator('^[a-zA-Z0-9-]+$',
+                                                                message = "Разрешены только латиница, цифры или тире")],
+                                error_messages={
+                                        'required': 'Обязательное поле',
+                                        'unique': 'Данный логин занят'
+                                })
+    email = forms.EmailField(label='Aдрем злектронной почты',
+                            error_messages={
+                                'invalid': 'Hе правильный формат адреса',
+                                'unique': 'Данный адрес занят'
+                            })
+    password = forms.CharField(label='Пароль',
+                                widget=forms.PasswordInput,
+                                validators=[validate_pasword_len],
+                                error_messages={
+                                    'required': 'Обязательное поле',
+                                })
+    password2 = forms.CharField(label='Пapоль (повторно)',
+                                widget=forms.PasswordInput,
+                                error_messages={
+                                    'required': 'Обязательное поле',
+                                })
+    rules = forms.BooleanField(required=True,
+                                label='Согласие с правилами регистрации',
+                               error_messages={
+                                   'required': 'Обязательное поле',
+                               })
+    def clean(self):
+        super().clean()
+        password = self.cleaned_data.get('password')
+        password2 = self.cleaned_data.get('password2')
+        if password and password2 and password != password2:
+            raise ValidationError({
+                'password2': ValidationError('Введенные пароли не совпадают', соdе='password_mismatch')
+        })
+    def save(self, commit=True):
+        user = super().save(commit=False)
+        user.set_password(self.cleaned_data.get('password'))
+        if commit:
+            user.save()
+        return user
+    class Meta:
+        model = User
+        fields = ('first_name', 'last_name', 'username', 'avatar', 'email', 'password', 'password2', 'rules')
+
+
+

+ 191 - 0
examen/exam/catalog/migrations/0001_initial.py

@@ -0,0 +1,191 @@
+# Generated by Django 5.0 on 2023-12-26 03:32
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+import django.db.models.deletion
+import django.utils.timezone
+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="Manufacturer",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=100)),
+            ],
+        ),
+        migrations.CreateModel(
+            name="User",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "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.UnicodeUsernameValidator()
+                        ],
+                        verbose_name="username",
+                    ),
+                ),
+                (
+                    "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(
+                        default=django.utils.timezone.now, verbose_name="date joined"
+                    ),
+                ),
+                ("first_name", models.CharField(max_length=254, verbose_name="Имя")),
+                ("last_name", models.CharField(max_length=254, verbose_name="Фамилия")),
+                (
+                    "avatar",
+                    models.ImageField(
+                        upload_to="avatars", verbose_name="Загрузите аватарку"
+                    ),
+                ),
+                ("email", models.CharField(max_length=254, verbose_name="Пoчтa")),
+                ("password", models.CharField(max_length=254, verbose_name="Пapoль")),
+                (
+                    "role",
+                    models.CharField(
+                        choices=[("admin", "Администратор"), ("user", "Пoльзователь")],
+                        default="user",
+                        max_length=254,
+                        verbose_name="Poль",
+                    ),
+                ),
+                (
+                    "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",
+                "abstract": False,
+            },
+            managers=[
+                ("objects", django.contrib.auth.models.UserManager()),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Product",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("title", models.CharField(max_length=200)),
+                (
+                    "summary",
+                    models.TextField(
+                        help_text="Enter a brief description of the product",
+                        max_length=1000,
+                    ),
+                ),
+                (
+                    "img",
+                    models.ImageField(
+                        blank=True, default=None, null=True, upload_to="img"
+                    ),
+                ),
+                (
+                    "isbn",
+                    models.CharField(
+                        help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>',
+                        max_length=13,
+                        verbose_name="ISBN",
+                    ),
+                ),
+                (
+                    "manufacturer",
+                    models.ForeignKey(
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        to="catalog.manufacturer",
+                    ),
+                ),
+            ],
+        ),
+    ]

+ 0 - 0
examen/exam/catalog/migrations/__init__.py


+ 38 - 0
examen/exam/catalog/models.py

@@ -0,0 +1,38 @@
+from django.db import models
+from django.urls import reverse
+from django.contrib.auth.models import User, AbstractUser
+
+
+class User(AbstractUser):
+    first_name = models.CharField(max_length=254, verbose_name='Имя', blank=False)
+    last_name = models.CharField(max_length=254, verbose_name='Фамилия', blank=False)
+    avatar = models.ImageField(verbose_name="Загрузите аватарку", upload_to='avatars')
+    email = models.CharField(max_length=254, verbose_name='Пoчтa', blank=False)
+    password = models.CharField(max_length=254, verbose_name='Пapoль', blank=False)
+    role = models.CharField(max_length=254, verbose_name='Poль',
+                            choices=(('admin', 'Администратор'), ('user', 'Пoльзователь')), default='user')
+
+
+class Product(models.Model):
+    title = models.CharField(max_length=200)
+    manufacturer = models.ForeignKey('manufacturer', on_delete=models.SET_NULL, null=True)
+    summary = models.TextField(max_length=1000, help_text="Enter a brief description of the product")
+    img = models.ImageField(upload_to='img', null=True, blank=True, default=None)
+    isbn = models.CharField('ISBN', max_length=13, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')
+    def __str__(self):
+
+        return self.title
+
+    def get_absolute_url(self):
+
+        return reverse('product-detail', args=[str(self.id)])
+
+class Manufacturer(models.Model):
+    name = models.CharField(max_length=100)
+
+    def get_absolute_url(self):
+        return reverse('manufacturer-detail', args=[str(self.id)])
+
+
+    def __str__(self):
+        return '%s' % (self.name)

+ 5 - 0
examen/exam/catalog/static/css/styles.css

@@ -0,0 +1,5 @@
+.sidebar-nav {
+  margin-top: 20px;
+  padding: 0;
+  list-style: none;
+}

+ 18 - 0
examen/exam/catalog/templates/accounts/profile.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Личный кабинет</title>
+</head>
+<body>
+    <h1>Привет, это личный кабинет</h1>
+    <br><br><br>
+    <a href ="{% url 'polls:del_prof' request.user.pk %}"><h3>Удалить профиль</h3></a>
+    <br><br><br>
+    <form method="post">
+        {% csrf_token %}
+        {{ form.as_p }}
+        <button type="submit">Сохранить</button>
+    </form>
+</body>
+</html>

+ 42 - 0
examen/exam/catalog/templates/base_generic.html

@@ -0,0 +1,42 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    {% block title %}<title>Local Library</title>{% endblock %}
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <link
+      rel="stylesheet"
+      href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
+    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
+    {% load static %}
+    <link rel="stylesheet" href="{% static 'css/styles.css' %}" />
+  </head>
+
+  <body>
+    <div class="container-fluid">
+      <div class="row">
+        <div class="col-sm-2">
+          {% block sidebar %}
+            <ul class="sidebar-nav">
+              <li><a href="{% url 'index' %}">Home</a></li>
+              <li><a href="{% url 'products' %}">All Products</a></li>
+              <li><a href=" +">About us</a></li>
+            </ul>
+
+
+     <li>User</li>
+     <li><a href="{% url 'logout'%}?next={{request.path}}">Logout</a></li>
+
+     <li><a href="{% url 'login'%}?next={{request.path}}">Login</a></li>
+            <p><a href="{% url 'registration'%}">Регистрация</a></p>
+
+  </ul>
+
+          {% endblock %}
+        </div>
+        <div class="col-sm-10 ">{% block content %}{% endblock %}</div>
+      </div>
+    </div>
+  </body>
+</html>

+ 17 - 0
examen/exam/catalog/templates/catalog/product_detail.html

@@ -0,0 +1,17 @@
+{% extends "base_generic.html" %}
+
+{% block content %}
+  <h1>Title: {{ product.title }}</h1>
+
+  <p><strong>Manufacturer:</strong> <a href="">{{ product.manufacturer }}</a></p> <!-- author detail link not yet defined -->
+  <p><strong>Summary:</strong> {{ product.summary }}</p>
+  <p><strong>ISBN:</strong> {{ product.isbn }}</p>
+{% if img %}
+  <img src="{{ img.url }}" alt="some_image" style="width:200px;">
+{% endif %}
+{% if not forloop.last %} {% endif %}</p>
+
+  <div style="margin-left:20px;margin-top:20px">
+
+  </div>
+{% endblock %}

+ 19 - 0
examen/exam/catalog/templates/catalog/product_list.html

@@ -0,0 +1,19 @@
+{% extends "base_generic.html" %}
+
+{% block content %}
+    <h1>Product List</h1>
+
+    {% if product_list %}
+    <ul>
+
+      {% for product in product_list %}
+      <li>
+        <a href="{{ product.get_absolute_url }}">{{ product.title }}</a> ({{product.manufacturer}})
+      </li>
+      {% endfor %}
+
+    </ul>
+    {% else %}
+      <p>There are no products in the library.</p>
+    {% endif %}
+{% endblock %}

+ 13 - 0
examen/exam/catalog/templates/index.html

@@ -0,0 +1,13 @@
+{% extends "base_generic.html" %}
+
+{% block content %}
+<h1>Local Library Home</h1>
+  <p>Welcome to <em>Магазин</em>, мы продаем лучшие товары по выгодной цене.</p>
+<h2>Dynamic content</h2>
+  <p>Статистика сайта:</p>
+  <ul>
+    <li><strong>product:</strong> {{ num_product }}</li>
+    <li><strong>manufacturer:</strong> {{ num_manufacturer }}</li>
+  </ul>
+{% endblock %}
+

+ 10 - 0
examen/exam/catalog/templates/profile.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Личный кабинет</title>
+</head>
+<body>
+    <h1>Привет!</h1>
+</body>
+</html>

+ 5 - 0
examen/exam/catalog/templates/registration/logged_out.html

@@ -0,0 +1,5 @@
+{% extends "base_generic.html" %}
+{% block content %}
+  <p>Logged out!</p>
+  <a href="{% url 'login'%}">Click here to login again.</a>
+{% endblock %}

+ 27 - 0
examen/exam/catalog/templates/registration/login.html

@@ -0,0 +1,27 @@
+{% extends "base_generic.html" %}
+
+{% block content %}
+
+{% if form.errors %}
+  <p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+<form method="post" action="{% url 'login' %}">
+{% csrf_token %}
+<table>
+
+<tr>
+  <td>{{ form.username.label_tag }}</td>
+  <td>{{ form.username }}</td>
+</tr>
+
+<tr>
+  <td>{{ form.password.label_tag }}</td>
+  <td>{{ form.password }}</td>
+</tr>
+</table>
+
+<input type="submit" value="login" />
+<input type="hidden" name="next" value="{{ next }}" />
+</form>
+{% endblock %}

+ 6 - 0
examen/exam/catalog/templates/registration/password_reset_complete.html

@@ -0,0 +1,6 @@
+{% extends "base_generic.html" %}
+
+{% block content %}
+  <h1>The password has been changed!</h1>
+  <p><a href="{% url 'login' %}">log in again?</a></p>
+{% endblock %}

+ 36 - 0
examen/exam/catalog/templates/registration/password_reset_confirm.html

@@ -0,0 +1,36 @@
+{% extends "base_generic.html" %}
+
+{% block content %}
+  {% if validlink %}
+    <p>Please enter (and confirm) your new password.</p>
+    <form action="" method="post">
+      {% csrf_token %}
+      <table>
+        <tr>
+          <td>
+            {{ form.new_password1.errors }}
+            <label for="id_new_password1">New password:</label>
+          </td>
+          <td>{{ form.new_password1 }}</td>
+        </tr>
+        <tr>
+          <td>
+            {{ form.new_password2.errors }}
+            <label for="id_new_password2">Confirm password:</label>
+          </td>
+          <td>{{ form.new_password2 }}</td>
+        </tr>
+        <tr>
+          <td></td>
+          <td><input type="submit" value="Change my password" /></td>
+        </tr>
+      </table>
+    </form>
+  {% else %}
+    <h1>Password reset failed</h1>
+    <p>
+      The password reset link was invalid, possibly because it has already been
+      used. Please request a new password reset.
+    </p>
+  {% endif %}
+{% endblock %}

+ 8 - 0
examen/exam/catalog/templates/registration/password_reset_done.htm

@@ -0,0 +1,8 @@
+{% extends "base_generic.html" %}
+
+{% block content %}
+  <p>
+    We've emailed you instructions for setting your password. If they haven't
+    arrived in a few minutes, check your spam folder.
+  </p>
+{% endblock %}

+ 2 - 0
examen/exam/catalog/templates/registration/password_reset_email.html

@@ -0,0 +1,2 @@
+Someone asked for password reset for email {{ email }}. Follow the link below:
+{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}

+ 9 - 0
examen/exam/catalog/templates/registration/password_reset_form.html

@@ -0,0 +1,9 @@
+{% extends "base_generic.html" %}
+
+{% block content %}
+  <form action="" method="post">{% csrf_token %}
+      {% if form.email.errors %} {{ form.email.errors }} {% endif %}
+          <p>{{ form.email }}</p>
+      <input type="submit" class="btn btn-default btn-lg" value="Reset password" />
+  </form>
+{% endblock %}

+ 34 - 0
examen/exam/catalog/templates/registration/registration.html

@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+
+{% block content %}
+    <h1>Регистрация</h1>
+    <form method="post">
+      {{ form.as_p}}
+      {% csrf_token %}
+      <button
+          type="submit"
+          class="btn btn-primary">
+          Регистрация
+      </button>
+    </form>
+{% endblock %}
+<script>
+    document.querySelector('#id_username').addEventListener('blur', async (event) => {
+        const res = await fetch('/validate_username?username=${event.target.value}).then(res => res.json());
+
+        event.target.parentNode.querySelector('.errorList')?.remove();
+        const errors = document.createElement('ul');
+        errors.classList.add('errorList');
+        if (res.is_taken) {
+            errors.innerHTML += '<li>Данный логин занят</li>';
+        }
+        if (event.target.value.length == 0) {
+            errors.innerHTML += '<li>Лoгин не может быть пустым</li>';
+        }
+        const re= new RegExp("^[a-zA-Z0-9-]+$");
+        if (!re.test(event.target.value)) {
+            errors.innerHTML += '<li>Разрешены только латиница, цифры или тире</li>';
+        }
+        event.target.parentNode.prepend(errors);
+        })
+</script>

+ 3 - 0
examen/exam/catalog/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 22 - 0
examen/exam/catalog/urls.py

@@ -0,0 +1,22 @@
+from django.conf import settings
+from django.conf.urls.static import static
+from django.urls import path
+from . import views
+from .views import validate_username
+from django.contrib.auth.views import LoginView
+from django.contrib.auth.views import LogoutView
+from django.urls import re_path as url
+from .views import *
+
+
+
+urlpatterns = [
+    path('', views.index, name='index'),
+    path('product/', views.ProductListView.as_view(), name='products'),
+    path(r'^product/(?P<pk>\d+)$', views.ProductDetailView.as_view(), name='product-detail'),
+    path('login/', LoginView.as_view(), name='login'),
+    path('registration/', views.registration, name='registration'),
+    path('register', views.RegisterView.as_view(), name='registration'),
+    path('validate_username', validate_username, name='validate_username'),
+    path('logout/', BBLogoutView.as_view(), name='logout'),
+]

+ 6 - 0
examen/exam/catalog/validators.py

@@ -0,0 +1,6 @@
+from django.core.exceptions import ValidationError
+
+
+def validate_pasword_len(password):
+    if len(password) < 6:
+        raise ValidationError('Длина пароля не может быть меньше 6 символов')

+ 54 - 0
examen/exam/catalog/views.py

@@ -0,0 +1,54 @@
+from django.contrib.auth.models import User
+from django.http import JsonResponse
+from django.urls import reverse_lazy
+from .models import Product, Manufacturer
+from django.views import generic
+from django.shortcuts import render
+from django.contrib.auth.views import LogoutView
+from django.views.generic import CreateView
+from .forms import RegisterUserForm
+
+class ProductDetailView(generic.DetailView):
+    model = Product
+
+class ProductListView(generic.ListView):
+    model = Product
+
+def index(request):
+    num_product=Product.objects.all().count()
+    num_manufacturer=Manufacturer.objects.count()
+    return render(
+        request,
+        'index.html',
+        context={'num_product':num_product, 'num_manufacturer':num_manufacturer},
+    )
+
+
+def login(request):
+    return render(request, 'registration/login.html')
+
+
+def registration(request):
+    return render(request, 'registration.html')
+
+def logout(request):
+    return render(request, 'logout.html')
+
+
+class RegisterView(CreateView):
+    template_name = 'registration.html'
+    form_class = RegisterUserForm
+    success_url = reverse_lazy('login')
+
+
+def validate_username(request):
+    username = request.GET.get('username', None)
+    response = {
+        'is_taken': User.objects.filter(username__iexact=username).exists()
+    }
+    return JsonResponse(response)
+
+
+class BBLogoutView(LogoutView):
+    template_name = 'logout.html'
+    success_url = reverse_lazy('index')

BIN
examen/exam/db.sqlite3


+ 0 - 0
examen/exam/exam/__init__.py


+ 16 - 0
examen/exam/exam/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for exam project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "exam.settings")
+
+application = get_asgi_application()

+ 129 - 0
examen/exam/exam/settings.py

@@ -0,0 +1,129 @@
+"""
+Django settings for exam project.
+
+Generated by 'django-admin startproject' using Django 5.0.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.0/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/5.0/ref/settings/
+"""
+import os
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = "django-insecure-ncrhed%6z@#4$9kmed(10o79kzclilljdj8ooobs6+5l*6&s-!"
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    "django.contrib.admin",
+    "django.contrib.auth",
+    "django.contrib.contenttypes",
+    "django.contrib.sessions",
+    "django.contrib.messages",
+    "django.contrib.staticfiles",
+    'catalog.apps.CatalogConfig',
+]
+
+MIDDLEWARE = [
+    "django.middleware.security.SecurityMiddleware",
+    "django.contrib.sessions.middleware.SessionMiddleware",
+    "django.middleware.common.CommonMiddleware",
+    "django.middleware.csrf.CsrfViewMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
+    "django.contrib.messages.middleware.MessageMiddleware",
+    "django.middleware.clickjacking.XFrameOptionsMiddleware",
+]
+
+ROOT_URLCONF = "exam.urls"
+
+TEMPLATES = [
+    {
+        "BACKEND": "django.template.backends.django.DjangoTemplates",
+        "DIRS": [os.path.join(BASE_DIR, 'templates')],
+        "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",
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = "exam.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
+
+DATABASES = {
+    "default": {
+        "ENGINE": "django.db.backends.sqlite3",
+        "NAME": BASE_DIR / "db.sqlite3",
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/5.0/topics/i18n/
+
+LANGUAGE_CODE = "en-us"
+
+TIME_ZONE = "UTC"
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/5.0/howto/static-files/
+
+STATIC_URL = "static/"
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
+
+LOGIN_REDIRECT_URL = '/'
+LOGOUT_REDIRECT_URL='/'
+
+AUTH_USER_MODEL = 'catalog.User'

+ 30 - 0
examen/exam/exam/urls.py

@@ -0,0 +1,30 @@
+"""
+URL configuration for exam project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/5.0/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path
+from django.urls import 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('catalog/', include('catalog.urls')),
+    path('', RedirectView.as_view(url='/catalog/', permanent=True)),
+    path('accounts/', include('django.contrib.auth.urls')),
+]
+urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

+ 16 - 0
examen/exam/exam/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for exam project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "exam.settings")
+
+application = get_wsgi_application()

+ 22 - 0
examen/exam/manage.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "exam.settings")
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == "__main__":
+    main()