{"id":25070763,"url":"https://github.com/eroydev/django-advanced","last_synced_at":"2025-10-23T20:47:10.097Z","repository":{"id":269599738,"uuid":"904949441","full_name":"ERoydev/Django-Advanced","owner":"ERoydev","description":null,"archived":false,"fork":false,"pushed_at":"2024-12-27T20:42:36.000Z","size":21167,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-06T16:01:50.726Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ERoydev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-12-17T21:30:03.000Z","updated_at":"2024-12-27T20:36:08.000Z","dependencies_parsed_at":"2025-06-06T16:12:14.193Z","dependency_job_id":null,"html_url":"https://github.com/ERoydev/Django-Advanced","commit_stats":null,"previous_names":["eroydev/django-advanced"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ERoydev/Django-Advanced","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ERoydev%2FDjango-Advanced","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ERoydev%2FDjango-Advanced/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ERoydev%2FDjango-Advanced/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ERoydev%2FDjango-Advanced/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ERoydev","download_url":"https://codeload.github.com/ERoydev/Django-Advanced/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ERoydev%2FDjango-Advanced/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260090544,"owners_count":22957243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-06T21:34:28.639Z","updated_at":"2025-10-23T20:47:05.057Z","avatar_url":"https://github.com/ERoydev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Django-Advanced\r\n\r\n---\r\n\r\n### Workshops\r\n- [Petstagram Repo](https://github.com/DiyanKalaydzhiev23/petstagram-2024.git)\r\n\r\n### Django REST JWT FE\r\n\r\n- [Django REST FE](https://github.com/DiyanKalaydzhiev23/JWT-Django-REST-FE-Softuni)\r\n\r\n---\r\n\r\n### Theory Tests\r\n\r\n- [Authentication and Authorization](https://forms.gle/VX4QFtCwmg2NApnx8)\r\n\r\n---\r\n\r\n- [User Model and Password Management](https://forms.gle/kuLJxNqj3AE2sn9h9)\r\n\r\n---\r\n\r\n- [Extending User Model](https://forms.gle/yLaStauEmS6iZsKm6)\r\n\r\n---\r\n\r\n- [Middlewares and Sessions](https://forms.gle/QdK1qzN3qo4HfkU3A)\r\n\r\n---\r\n\r\n- [Django REST Basics](https://forms.gle/9ou2q8U97LmfQHMs9)\r\n\r\n---\r\n\r\n- [Django REST Advanced](https://forms.gle/h5TvbecgszVhjfau5)\r\n\r\n---\r\n\r\n- [Asynchronous Operations](https://forms.gle/jbcABaRa4LmEK6rcA)\r\n\r\n---\r\n\r\n- [Unit and Integration Testing](https://forms.gle/cuSanBrcTUya5cYX6)\r\n\r\n---\r\n\r\n- [Deployment Setup](https://forms.gle/DAkp3wNGAAQfDj2r7)\r\n\r\n---\r\n\r\n# Plans\r\n\r\n\r\n### Authentication and Autorization\r\n\r\n1. Какво означават?\r\n   - Ауторизация е проверката за това какви права иначе като потребители\r\n   - Аутентикацията е проверката за това кои сме ние (логване в профил)\r\n\r\n2. Видове credentials\r\n   - Потребителско име и парола - Single-factor authentication\r\n   - Tелефонен номер, на който се изпраща парола - Multi-factor authentication\r\n \r\n3. Authentication in Django\r\n   - django.contrib.auth\r\n   - Е допълнителен пакет, както админа\r\n   - Дава ни permissions, groups, users\r\n   - Cookie Based user session handling\r\n      - При логин `Django` създава ключ към сесията и го пази в coоkie, което пази в таблица django_session бекенда и на всяка заявка го изпраща и сравнява със session middleware, за да знае от кой е изпратено\r\n      - SESSION_COOKIE_HTTPONLY = True - позволява изпращането на session_key само през https\r\n      - CSRF_COOKIE_HTTPONLY = True - Не позволява на бразъра да достъпва кукито през document.cookie \r\n   - AuthMiddleware взима потребителя\r\n   - Работи заедно с django.contrib.contenttypes\r\n  \r\n\r\n4. Django Permissions \r\n   - Имаме таблица с permissions, всеки път когато направим нов модел се добавят нови permissions\r\n   - Те представляват позволения за CRUD операции\r\n\r\n5.  Web security\r\n   1. SQL инжекция (SQL Injection)\r\n      - SQL инжекцията е атака, при която злонамерен потребител въвежда зловреден SQL код в полета за въвеждане на данни (като форми за логин), с цел да манипулира или извлече данни от базата данни. Тази уязвимост възниква, когато приложението не валидира или не пречиства потребителския вход правилно.\r\n      \r\n   2. Кроссайт скриптиране (XSS)\r\n      - Кроссайт скриптирането е атака, при която злонамерен потребител вкарва зловреден скрипт (обикновено JavaScript) в уебсайт, който след това се изпълнява от браузъра на други потребители. Това може да доведе до кражба на бисквитки, манипулация на съдържание или пренасочване към зловредни сайтове.\r\n   \r\n   3. URL/HTTP манипулационни атаки (Промяна на параметри - Parameter Tampering)\r\n      - При този вид атака, нападателят манипулира URL и ли параметри в HTTP заявка, за да получи неоторизиран достъп до ресурси или да промени поведението на приложението. Например, промяна на параметър в URL, който определя цената на продукт, за да се закупи нещо на по-ниска цена.\r\n   \r\n   4. Кроссайт заявка за фалшификация (CSRF)\r\n      - CSRF атаката принуждава потребител, който е логнат в уеб приложение, да извърши неволно действие (като изпращане на форма или извършване на плащане), без неговото знание. Това се постига чрез изпращане на специално създадена връзка или форма към потребителя.\r\n   \r\n   5. Атаки с груба сила (Brute Force Attacks) и DDoS (Разпределени атаки за отказ от услуга)\r\n      - При атака с груба сила, нападателят автоматично опитва множество комбинации от пароли или ключове, докато не намери правилната. DDoS атаките целят да претоварят уебсайт или услуга с огромен брой заявки, което да доведе до забавяне или пълно прекъсване на услугата.\r\n   \r\n   6. Недостатъчен контрол на достъпа (Insufficient Access Control)\r\n      - Недостатъчният контрол на достъпа е уязвимост, при която потребители или системи получават достъп до ресурси или функционалности, за които нямат разрешение. Това може да доведе до изтичане на конфиденциална информация или изпълнение на неоторизирани действия.\r\n   \r\n   7. Липса на SSL (HTTPS) / Атаки Човек в средата (MITM)\r\n      - Липсата на SSL (HTTPS) прави връзката между потребителя и уебсайта незащитена, което позволява на нападател да прихване, промени или открадне данни (като пароли или лична информация) по време на предаването. MITM атаката възниква, когато нападателят се позиционира между комуникиращите страни и тайно следи или манипулира комуникацията.\r\n   \r\n   8. Фишинг/Социално инженерство (Phishing/Social Engineering)\r\n      - Фишингът и социалното инженерство са методи, при които нападателят измамно убеждава потребителя да разкрие чувствителна информация (като пароли или номера на кредитни карти) или да извърши определено действие (като инсталиране на зловреден софтуер), като се представя за доверено лице или организация.\r\n   \r\n---\r\n\r\n### User Model and Password Management\r\n\r\n1. Built-in Django User\r\n   - User(AbstractUser)\r\n     - Може да бъде намерен в моделите на django.auth app-a\r\n     - таблица auth_users\r\n     - Имаме го във всяка заявка и можем да го достъпим с request.user\r\n     - Django ни позволява да променяме вградения потребителски модел на няколко нива\r\n       - Можем само да го надградим наследявайки AbstractUser или изцяло да го заменим наследявайки AbstractBaseUser\r\n     - Дава ни PermissionsMixin, който вграденият User модел наследява.\r\n       - Той се грижи за това дали потребителя е superuser, какви права има и в какви групи е\r\n       - Дава ни **staff_member_required** декоратор\r\n     - USERNAME_FIELD ни позволява да презапишем полете, което ще се използва за първи креденшъл\r\n     - email_user() ни позволява да изпращаме имейли на потребителите след настройка на SMTP\r\n     - **AnonymusUser**, който не е модел, но клас, който презаписва всички атрибути на базовия клас\r\n     - Дава ни 2 основни функции\r\n       - login - закача cookie за аутентикирания  потребител\r\n       - authenticate - проверява дали креденшълите на потребителя са верни\r\n     - get_user_model() - дава ни модела, който се използва за user в апликацията\r\n\r\n2. Login\r\n   - Django ни дава готово **LoginView**\r\n   - Когато ползваме LoginView получаваме следните параметри:\r\n     - next - помага ни да редиректнем потребителя към view-то, което се е опитал да достъпи преди да е бил логнат\r\n     - site - url-a на уебсайта\r\n  \r\n3. Register\r\n   - Нямаме view за регистрация, но имаме форма\r\n   ```py\r\n   class UserRegisterView(CreateView):\r\n       form_class = UserCreationForm\r\n       template_name = 'registration/register.html'\r\n       success_url = reverse_lazy('login')\r\n\r\n\r\n   # settings.py - optional\r\n   LOGIN_REDIRECT_URL = '/'\r\n   LOGOUT_REDIRECT_URL = '/'\r\n\r\n    \u003cform method=\"post\" action=\"{% url 'login' %}{% if next %}?next={{ next }}{% endif %}\"\u003e\r\n     {% csrf_token %}\r\n     {{ form.as_p }}\r\n     \u003cbutton type=\"submit\"\u003eLogin\u003c/button\u003e\r\n    \u003c/form\u003e\r\n\r\n\r\n   ```\r\n   - Формата обаче работи само с User-a от Django, но има как да променим това\r\n   ```py\r\n      class CustomUserCreationForm(UserCreationForm):\r\n          class Meta(UserCreationForm.Meta):\r\n              model = get_user_model()  # Use the custom user model\r\n              fields = ('username', 'email') \r\n   ```\r\n\r\n4. Passowords\r\n   - Използват one-way hash\r\n   - Имаме Views за промяна на паролите\r\n  \r\n5. Groups\r\n   - has_perm()\r\n   - PermissionsMixin\r\n   - permission_required() - декоратор\r\n\r\n---\r\n\r\n\r\n### Extending the user model\r\n\r\n`AUTH_USER_MODEL = 'path.to.my.model'`\r\n\r\n 1. Защо наследяваме AbstractUser, а не USER?\r\n    - Както помним от Python ORM, ако наследим неабстрактен модел, то ние получаваме 1-To-1 relationship\r\n    - Докато, ако е абстрактен, получаваме директно полетата в една таблица\r\n\r\n2. AbstractUser vs AbstractBaseUser\r\n   - AbstractUser е user-a, който познаваме, този който Django наследява. Той наследява AbstractBaseUser\r\n   - AbstractBaseUser съдържа само 2 полета, password и last_login\r\n  \r\n3. Начини за наследяване\r\n   - Чрез Proxy\r\n     - Pros: \r\n        - Можем да добавим методи и мета данни продължавайки да използваме модела от Django\r\n        - Няма нужда да пренаписваме Django Auth system\r\n     - Cons:\r\n        - Не можем да добавяме свои полета\r\n          \r\n   - Наследявайки AbstractUser или AbstractBaseUser\r\n     - Pros:\r\n        - Можем да добавяме свои полета\r\n        - Няма нужда да пренаписваме Django Auth system\r\n     - Cons:\r\n        - По-трудна миграция към друг auth model в бъдеще (например, ако искаме да сменим Django Sessions с JWT)\r\n          \r\n   - Наследяваки User в модел Profile\r\n     - Създаваки профил към всеки потребител чрез One-to-One\r\n     - Pros:\r\n        - Можем да добавяме свои полета\r\n        - По-лесна миграция към друг auth model в бъдеще (например, ако искаме да сменим Django Sessions с JWT)\r\n     - Cons:\r\n        - Трябва да пренаписваме Django Auth system\r\n      \r\n      - Може да стане по два начина:\r\n        - Наследявайки built-in user\r\n        - Създавайки наш user\r\n       \r\n      - Ще ни трябва да променим register платформата\r\n     ```py\r\n           class CustomUserCreationForm(UserCreationForm):\r\n             profile_field = forms.FieldType()\r\n     \r\n             class Meta(UserCreationForm.Meta):\r\n                 model = get_user_model()  # Use the custom user model\r\n                 fields = ('username', 'email')\r\n\r\n              def save(self, commit=True):\r\n                 user = super().save(commit=commit)\r\n\r\n                 profile = Profile(\r\n                     user=user,\r\n                     age=self.cleaned_data[\"age\"]\r\n                 )\r\n\r\n                 if commit:\r\n                    profile.save()\r\n\r\n              retrun user\r\n     ```\r\n\r\n4. User с AbstractBaseUser\r\n   ##### Step 1: Create a model and a manager\r\n   ```py\r\n   from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin\r\n   from django.db import models\r\n   from django.utils.translation import gettext_lazy as _\r\n\r\n   class CustomUserManager(BaseUserManager):\r\n       def create_user(self, email, password=None, **extra_fields):\r\n           if not email:\r\n               raise ValueError(_('The Email field must be set'))\r\n           email = self.normalize_email(email)\r\n           user = self.model(email=email, **extra_fields)\r\n           user.set_password(password)\r\n           user.save(using=self._db)\r\n           return user\r\n   \r\n       def create_superuser(self, email, password=None, **extra_fields):\r\n           extra_fields.setdefault('is_staff', True)\r\n           extra_fields.setdefault('is_superuser', True)\r\n   \r\n           if extra_fields.get('is_staff') is not True:\r\n               raise ValueError(_('Superuser must have is_staff=True.'))\r\n           if extra_fields.get('is_superuser') is not True:\r\n               raise ValueError(_('Superuser must have is_superuser=True.'))\r\n   \r\n           return self.create_user(email, password, **extra_fields)\r\n\r\n   class CustomUser(AbstractBaseUser, PermissionsMixin):\r\n       email = models.EmailField(unique=True)\r\n       first_name = models.CharField(max_length=30, blank=True)\r\n       last_name = models.CharField(max_length=30, blank=True)\r\n       is_active = models.BooleanField(default=True)\r\n       is_staff = models.BooleanField(default=False)\r\n       date_joined = models.DateTimeField(auto_now_add=True)\r\n   \r\n       objects = CustomUserManager()\r\n   \r\n       USERNAME_FIELD = 'email'\r\n       REQUIRED_FIELDS = []\r\n   \r\n       def __str__(self):\r\n           return self.email\r\n\r\n   ```\r\n\r\n   ##### Step 2: Configure settings\r\n   ```py\r\n   AUTH_USER_MODEL = 'accounts.CustomUser'\r\n   ````\r\n\r\n   \r\n   ##### Step 3: Modify the User Creation Form\r\n      - In accounts/forms.py, import get_user_model() and use it to define the form class:\r\n      ```py\r\n      class CustomUserCreationForm(UserCreationForm):\r\n          class Meta:\r\n              model = get_user_model()  # Dynamically get the user model\r\n              fields = ('email', 'first_name', 'last_name')\r\n      ```\r\n\r\n   ##### Step 4: Update the Registration View\r\n      - Ensure your registration view is correctly set up to use the CustomUserCreationForm:\r\n      - In accounts/views.py:\r\n      ```py\r\n      class RegisterView(CreateView):\r\n          form_class = CustomUserCreationForm\r\n          template_name = 'accounts/register.html'\r\n          success_url = reverse_lazy('login')\r\n\r\n            \r\n      class CustomLoginView(LoginView):\r\n          form_class = CustomUserLoginForm\r\n          template_name = 'accounts/login.html'\r\n      ```\r\n\r\n   ##### Step 5: Admin\r\n      After creating a custom user model, you also need to configure the Django admin to manage users via the admin interface. \r\n      \r\n      In your `accounts/admin.py`, register your custom user model with the Django admin interface. You need to create a custom `ModelAdmin` class to specify how the model should be displayed in the admin interface.\r\n      \r\n      ### `accounts/admin.py`\r\n      \r\n      ```python\r\n         from django.contrib import admin\r\n         from django.contrib.auth.admin import UserAdmin as BaseUserAdmin\r\n         from django.contrib.auth import get_user_model\r\n         from django.utils.translation import gettext_lazy as _\r\n         from .forms import CustomUserCreationForm\r\n         \r\n         CustomUser = get_user_model()\r\n         \r\n         class CustomUserAdmin(BaseUserAdmin):\r\n             add_form = CustomUserCreationForm\r\n             form = CustomUserCreationForm\r\n             model = CustomUser\r\n             list_display = ('email', 'first_name', 'last_name', 'is_staff', 'is_active')\r\n             list_filter = ('is_staff', 'is_active')\r\n             fieldsets = (\r\n                 (None, {'fields': ('email', 'password')}),\r\n                 (_('Personal Info'), {'fields': ('first_name', 'last_name')}),\r\n                 (_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'groups', 'user_permissions')}),\r\n                 (_('Important dates'), {'fields': ('last_login', 'date_joined')}),\r\n             )\r\n             add_fieldsets = (\r\n                 (None, {\r\n                     'classes': ('wide',),\r\n                     'fields': ('email', 'password1', 'password2', 'first_name', 'last_name', 'is_staff', 'is_active')}\r\n                 ),\r\n             )\r\n             search_fields = ('email',)\r\n             ordering = ('email',)\r\n         \r\n         admin.site.register(CustomUser, CustomUserAdmin)\r\n      ```\r\n\r\n5. Signals\r\n   - Publish-Subscribe Pattern\r\n   - Имаме няколко типа сигнали:\r\n     - model\r\n     - request\r\n     - management\r\n     - etc...\r\n   - Като се случи някакво събитие да се изпълни даден код\r\n   ```py\r\n      # accounts/models.py\r\n      from django.conf import settings\r\n      from django.db import models\r\n      from django.contrib.auth import get_user_model\r\n      \r\n      class Profile(models.Model):\r\n          user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)\r\n          bio = models.TextField(blank=True)\r\n          location = models.CharField(max_length=100, blank=True)\r\n      \r\n          def __str__(self):\r\n              return self.user.email\r\n\r\n      # accounts/signals.py\r\n   \r\n      from django.db.models.signals import post_save\r\n      from django.dispatch import receiver\r\n      from django.conf import settings\r\n      from .models import Profile\r\n      \r\n      @receiver(post_save, sender=settings.AUTH_USER_MODEL)\r\n      def create_profile(sender, instance, created, **kwargs):\r\n          if created:\r\n              Profile.objects.create(user=instance)\r\n\r\n      # accounts/apps.py\r\n\r\n      from django.apps import AppConfig\r\n      \r\n      class AccountsConfig(AppConfig):\r\n          default_auto_field = 'django.db.models.BigAutoField'\r\n          name = 'accounts'\r\n      \r\n          def ready(self):\r\n              import accounts.signals\r\n   ```\r\n   \r\n---\r\n\r\n\r\n### Django Middlewares and Sessions\r\n\r\n1. Какво е Middleware?\r\n   - Функционалност, която се изпълнява преди и/или след **всеки** рекуест\r\n   - Доста наподобява това да направим миксин, но с middleware-a няма нужда да го наследяваме никъде. Тоест става абстрактно.\r\n   - Реда на изпълнение е важен\r\n   - Middleware-ите се изпълняват top-down, преди заявката и bottom up след нея.\r\n   - Задава се в `settings.py`\r\n   ```py\r\n   MIDDLEWARES = [\r\n      ...,\r\n      Path.to.your.callable,  # callable - something that overwrites the method __call__\r\n      ...,\r\n   ]\r\n   ```\r\n\r\n   - Template for function middleware\r\n   ```py\r\n      def measure_time(get_response):\r\n         def middleware(request, *args, **kwargs): // looks like view\r\n            result = get_response()\r\n\r\n            return result\r\n\r\n      return middleware\r\n   ```\r\n\r\n   - Example\r\n   ```py\r\n      def measure_time(get_response):\r\n         def middleware(request, *args, **kwargs): // looks like view\r\n            start_time = time.time()\r\n            result = get_response(request)\r\n            end_time = time.time()\r\n\r\n            print(f\"{request.path} executed in {end_time - start_time} seconds.\")\r\n   \r\n            return result\r\n\r\n      return middleware\r\n   ```\r\n\r\n   - С клас\r\n   ```py\r\n   import time\r\n\r\n   class RequestTimingMiddleware(MiddlewareMixin):\r\n       \"\"\"\r\n       Middleware to measure the time taken to process a request using process_request and process_response.\r\n       \"\"\"\r\n\r\n       def __init__(self, get_response):\r\n           self.get_response = get_response\r\n   \r\n       def process_request(self, request):\r\n           # Start time before processing the request\r\n           self.start_time = time.time()\r\n   \r\n       def process_response(self, request, response):\r\n           # End time after processing the request\r\n           end_time = time.time()\r\n   \r\n           # Calculate the duration\r\n           duration = end_time - self.start_time\r\n   \r\n           # Log the duration (you can use any logging mechanism here)\r\n           print(f\"Request to {request.path} took {duration:.4f} seconds.\")\r\n   \r\n           return response\r\n\r\n   ```\r\n\r\n   - Освен process_request и process_response, имаме и process_view, което ни позволява да изпълняваме код точно преди view-то да се изпълни\r\n   - С други думи. Всеки middleware се вика три пъти. Преди request, точно преди view и след request.\r\n\r\n2. Session\r\n   - HTTP e stateless - не пази никаква информация, всяка заявка е сама за себе си.\r\n   - Сесията е начин по който сървъра може да пази информация за user-a.\r\n   - Сесията в Django е базово имплементирана.\r\n   - Имаме таблица `django_session`, която пази ключ- session key, който се подава на клиента, session_data - стойност с информация за потребителя, и expiration_date - дата, в която тази сесия изтича (default 2 седмици).\r\n   - Ако се логне от два браузъра, ще имаме две сесии за един потребител\r\n   - Сесията в базата е сериализирана, но когато я достъпваме във view тя се десериализира и можем да я третираме като обект\r\n   - Ключовете на обекти в сесията трябва да се string-ове и да нямат специални символи\r\n   ```py\r\n   def view_counter(request): \r\n    # Check if the 'counter' key exists in the session\r\n    if 'counter' in request.session:\r\n        # Increment the counter\r\n        request.session['counter'] += 1\r\n    else:\r\n        # Initialize the counter if it's the first visit\r\n        request.session['counter'] = 1 \r\n\r\n    counter = request.session['counter']\r\n\r\n    return HttpResponse(f\"View count: {counter}\")\r\n   ```\r\n\r\n3. Cookies\r\n   - Метаданни за даннте\r\n   - Браузъра ги праща на всяка заявка, към домейна за който са заяазени\r\n   - Пример: cookie sessionId запазено за localhost, всеки път ще праща сесията\r\n   - Cookie-та без дата на изтичане се изтриват на изтичане на сесията на браузъра\r\n   - Няма как да направим вечно куки\r\n   - `request.COOKIES`\r\n   ```py\r\n   from django.http import HttpResponse\r\n   from django.utils.timezone import now\r\n   from django.views import View\r\n   \r\n   class SetTimeCookieView(View):\r\n       def dispatch(self, request, *args, **kwargs):\r\n           # Call the parent dispatch method to get the response\r\n           response = super().dispatch(request, *args, **kwargs)\r\n   \r\n           # Get the current time\r\n           current_time = now()\r\n   \r\n           # Check if the 'last_visit' cookie exists\r\n           last_visit = request.COOKIES.get('last_visit')\r\n           if last_visit:\r\n               # If the cookie exists, add a message about the last visit time\r\n               response.content += f\"Your last visit was on: {last_visit}\u003cbr\u003e\".encode()\r\n           else:\r\n               # If this is the first visit, add a message indicating that\r\n               response.content += \"This is your first visit!\u003cbr\u003e\".encode()\r\n   \r\n           # Set the current time as a cookie named 'last_visit'\r\n           response.set_cookie('last_visit', current_time.strftime('%Y-%m-%d %H:%M:%S'))\r\n   \r\n           # Optionally, set the cookie to expire in a certain number of seconds (e.g., 1 day)\r\n           # response.set_cookie('last_visit', current_time.strftime('%Y-%m-%d %H:%M:%S'), max_age=86400)\r\n   \r\n           # Add a message about setting the cookie\r\n           response.content += f\"Setting the current time ({current_time.strftime('%Y-%m-%d %H:%M:%S')}) as a cookie.\".encode()\r\n   \r\n           return response\r\n\r\n   ```\r\n\r\n---\r\n\r\n\r\n---\r\n\r\n### Django REST Basics\r\n\r\n1. Какво е API?\r\n   - Application Programing Interface\r\n   - Начин по-който ние можем да се свържем с дадена система, множеството от функционалности, които ние можем да използваме на една система\r\n   - Абстрактен пример: \r\n     - Порта на телефон и кабел за зареждане. Телефона ни предоставя начин да го зареждаме (с кабел)\r\n\r\n2. Какво е REST?\r\n   - ReprEsentational State Transfer\r\n  \r\n3. REST API\r\n   - Множество от ендпойнти чрез, които ние можем да работим с дадена система \r\n   - Предимно се комуникира с JSON, но реално можем да изпращаме данни под различни формати\r\n   - REST API e stateless, тоест трябва да пазим всичко, което апи-то ни връща на клиента\r\n   - В заключение можем да имаме различни клиенти:\r\n     - Мобилно приложение\r\n     - Уеб приложение\r\n     - Пералня\r\n     - ...\r\n   - Но те всички да изпращат заявки към едно API, и то да им връща резултат като JSON\r\n   - Примери:\r\n     - [Stripe](https://docs.stripe.com/api/connected-accounts)\r\n     - [Swapi](https://swapi.dev/)\r\n     - [PokeApi](https://pokeapi.co/)\r\n     - [Weather API](https://www.visualcrossing.com/weather-api?gad_source=1\u0026gclid=CjwKCAjw59q2BhBOEiwAKc0ijb-nOtbeEpv4Mxv9iJdv6Okno4A4JNJiT1MATH_poGi5PHv2r32z9BoCF34QAvD_BwE)\r\n\r\n4. SPA vs MPA\r\n   - SPA - Single Page Application (Client Side Rendering)\r\n     - Приложение често направено с клиент на JS (в контекста на уеб), което за зареждане на нова информация изпраща заявка до API и не ярезарежда страницата.\r\n   - MPA - Multi Page Application (Server Side Rendering)\r\n     - Приложение, което всеки път, в който трябва да изпрати нова информация я рендерира в html на сървъра и врща като отговор този html. Това води до нуждата да презареждаме страницата, всеки път, когато нова информация е нужна или заявка трябва да бъде изпратена.\r\n    \r\n5. Methods\r\n   - В Django ползвахме форми, а формите в html поддържат само post и get\r\n   - Сега имаме възможност да ползваме всички методи\r\n  \r\n6. Django REST Framework\r\n   - Пакет, който ни позволява да създаваме REST API-та\r\n   - Доста близко до Django\r\n   - Дава ни сериализация\r\n     - Начин, по който нашия Django модел да се превърне в JSON обект и обратното\r\n   - Можем и да не го ползваме и да връщаме JSON през нормалното Django, но тогава нещата стават по-сложни, защото трябва да пренапишем всяко Generic view, понеже те връщат темплейт.\r\n   - `pip install djangorestframework`\r\n   ```py\r\n      INSTALLED_APPS = [\r\n         ...,\r\n         'rest_framework\r\n         ...,\r\n      ]\r\n   ```\r\n\r\n7. Serializers\r\n   - Начин, по който нашия Django модел да се превърне в JSON обект и обратно\r\n   - Както при формите имаме serializers и ModelSerializers\r\n   ```py\r\n   from rest_framework import serializers\r\n   from .models import Book\r\n   \r\n   class BookSerializer(serializers.ModelSerializer):\r\n       class Meta:\r\n           model = Book\r\n           fields = '__all__'\r\n\r\n   from rest_framework import generics\r\n   from .models import Book\r\n   from .serializers import BookSerializer\r\n   \r\n   class BookListCreate(generics.ListCreateAPIView):\r\n       queryset = Book.objects.all()\r\n       serializer_class = BookSerializer\r\n\r\n   // or with fbv\r\n\r\n   @api_view(['GET', 'POST'])\r\n   def book_list_create(request):\r\n       if request.method == 'GET':\r\n           books = Book.objects.all()\r\n           serializer = BookSerializer(books, many=True)\r\n\r\n           return Response(serializer.data)\r\n   \r\n       elif request.method == 'POST':\r\n           serializer = BookSerializer(data=request.data)\r\n   \r\n           if serializer.is_valid():\r\n               serializer.save()\r\n               return Response(serializer.data, status=status.HTTP_201_CREATED)\r\n\r\n           return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\r\n\r\n   ```\r\n\r\n5. Django Generics vs DRF Generics\r\n   - View\r\n   ```py\r\n      class SimpleView(View):\r\n          def get(self, request, *args, **kwargs):\r\n              context = {\r\n                  'message': 'Hello, this is a simple Django View!'\r\n              }\r\n              return render(request, 'simple_template.html', context)\r\n\r\n      class SimpleAPIView(APIView):\r\n          def get(self, request, *args, **kwargs):\r\n              data = {\r\n                  'message': 'Hello, this is a simple APIView response!'\r\n              }\r\n              return Response(data)\r\n   ```\r\n   - ListView\r\n   ```py\r\n      # Django\r\n      from django.views.generic import ListView\r\n      from .models import Book\r\n      \r\n      class BookListView(ListView):\r\n          model = Book\r\n          template_name = 'books/book_list.html'\r\n\r\n      # DRF\r\n      from rest_framework import generics\r\n      from .models import Book\r\n      from .serializers import BookSerializer\r\n      \r\n      class BookListAPIView(generics.ListAPIView):\r\n          queryset = Book.objects.all()\r\n          serializer_class = BookSerializer\r\n\r\n      @api_view(['GET'])\r\n      def book_list(request):\r\n          books = Book.objects.all()\r\n          serializer = BookSerializer(books, many=True)\r\n          return Response(serializer.data)\r\n   ```\r\n\r\n   - DetailView\r\n     ```py\r\n         from django.views.generic import DetailView\r\n         from .models import Book\r\n         \r\n         class BookDetailView(DetailView):\r\n             model = Book\r\n             template_name = 'books/book_detail.html'\r\n\r\n        # DRF\r\n         from rest_framework import generics\r\n         from .models import Book\r\n         from .serializers import BookSerializer\r\n         \r\n         class BookDetailAPIView(generics.RetrieveAPIView):\r\n             queryset = Book.objects.all()\r\n             serializer_class = BookSerializer\r\n\r\n         @api_view(['GET'])\r\n         def book_detail(request, pk):\r\n             try:\r\n                 book = Book.objects.get(pk=pk)\r\n             except Book.DoesNotExist:\r\n                 return Response(status=status.HTTP_404_NOT_FOUND)\r\n         \r\n             serializer = BookSerializer(book)\r\n             return Response(serializer.data)\r\n     ```\r\n\r\n   - CreateView\r\n   ```py\r\n      from django.views.generic import CreateView\r\n      from .models import Book\r\n      from .forms import BookForm\r\n      \r\n      class BookCreateView(CreateView):\r\n          model = Book\r\n          form_class = BookForm\r\n          template_name = 'books/book_form.html'\r\n          success_url = '/books/'\r\n\r\n      # DRF\r\n      from rest_framework import generics\r\n      from .models import Book\r\n      from .serializers import BookSerializer\r\n      \r\n      class BookCreateAPIView(generics.CreateAPIView):\r\n          queryset = Book.objects.all()\r\n          serializer_class = BookSerializer\r\n\r\n      \r\n      @api_view(['POST'])\r\n      def book_create(request):\r\n          serializer = BookSerializer(data=request.data)\r\n          if serializer.is_valid():\r\n              serializer.save()\r\n              return Response(serializer.data, status=status.HTTP_201_CREATED)\r\n          return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\r\n   ```\r\n\r\n   -UpdateView\r\n   ```py\r\n   from django.views.generic import UpdateView\r\n   from .models import Book\r\n   from .forms import BookForm\r\n   \r\n   class BookUpdateView(UpdateView):\r\n       model = Book\r\n       form_class = BookForm\r\n       template_name = 'books/book_form.html'\r\n       success_url = '/books/'\r\n\r\n   # DRF\r\n   from rest_framework import generics\r\n   from .models import Book\r\n   from .serializers import BookSerializer\r\n   \r\n   class BookUpdateAPIView(generics.UpdateAPIView):\r\n       queryset = Book.objects.all()\r\n       serializer_class = BookSerializer\r\n\r\n   @api_view(['PUT'])\r\n   def book_update(request, pk):\r\n       try:\r\n           book = Book.objects.get(pk=pk)\r\n       except Book.DoesNotExist:\r\n           return Response(status=status.HTTP_404_NOT_FOUND)\r\n   \r\n       serializer = BookSerializer(book, data=request.data)\r\n       if serializer.is_valid():\r\n           serializer.save()\r\n           return Response(serializer.data)\r\n       return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\r\n   ```\r\n\r\n   - DeleteView\r\n   ```py\r\n   class BookDeleteView(DeleteView):\r\n       model = Book\r\n       template_name = 'books/book_confirm_delete.html'\r\n       success_url = '/books/'\r\n\r\n   # DRF\r\n   class BookDeleteAPIView(generics.DestroyAPIView):\r\n      queryset = Book.objects.all()\r\n\r\n   @api_view(['DELETE'])\r\n   def book_delete(request, pk):\r\n       try:\r\n           book = Book.objects.get(pk=pk)\r\n       except Book.DoesNotExist:\r\n           return Response(status=status.HTTP_404_NOT_FOUND)\r\n   \r\n       book.delete()\r\n       return Response(status=status.HTTP_204_NO_CONTENT)\r\n   ```\r\n   \r\n---\r\n\r\n---\r\n\r\n### Django REST Advanced\r\n\r\n1. Видове сериалайзъри\r\n   1. Serializer\r\n      - Това е базовият клас за създаване на serializers. В него ръчно дефинирате полетата и начините за обработка на данните.\r\n     ```py\r\n         from rest_framework import serializers\r\n         \r\n         class UserSerializer(serializers.Serializer):\r\n             username = serializers.CharField(max_length=100)\r\n             email = serializers.EmailField()\r\n             is_active = serializers.BooleanField()\r\n         \r\n         # Сериализиране на данни:\r\n         user_data = {\r\n             \"username\": \"john\",\r\n             \"email\": \"john@example.com\",\r\n             \"is_active\": True\r\n         }\r\n         serializer = UserSerializer(data=user_data)\r\n         if serializer.is_valid():\r\n             print(serializer.validated_data)\r\n     ```\r\n\r\n   2. ModelSerializer\r\n      - ModelSerializer е опростена версия на Serializer, която автоматично създава полетата въз основа на Django модела. Спестява време при сериализация на данни от модели.\r\n      ```py\r\n         from rest_framework import serializers\r\n         from myapp.models import User\r\n         \r\n         class UserSerializer(serializers.ModelSerializer):\r\n             class Meta:\r\n                 model = User\r\n                 fields = ['username', 'email', 'is_active']\r\n\r\n      ```\r\n   3. ListSerializer\r\n      - ListSerializer се използва за сериализация на списъци с обекти. Обикновено той се използва вътрешно от Django REST Framework, когато сериализирате множество обекти, но може да бъде дефиниран и ръчно.\r\n      ```py\r\n      from rest_framework import serializers\r\n\r\n      class UserSerializer(serializers.Serializer):\r\n          username = serializers.CharField(max_length=100)\r\n      \r\n      class UserListSerializer(serializers.ListSerializer):\r\n          child = UserSerializer()\r\n      \r\n      data = [\r\n          {\"username\": \"john\"},\r\n          {\"username\": \"jane\"}\r\n      ]\r\n      serializer = UserListSerializer(data=data)\r\n      if serializer.is_valid():\r\n          print(serializer.validated_data)\r\n      ```\r\n\r\n   4. HyperlinkedModelSerializer\r\n      - HyperlinkedModelSerializer е подобен на ModelSerializer, но вместо да използва PrimaryKeyRelatedField за релации, използва HyperlinkedIdentityField за URL връзки към други ресурси.\r\n      ```py\r\n      from rest_framework import serializers\r\n      from myapp.models import User\r\n      \r\n      class UserSerializer(serializers.HyperlinkedModelSerializer):\r\n          class Meta:\r\n              model = User\r\n              fields = ['url', 'username', 'email', 'is_active']\r\n      ```\r\n   5. SlugRelatedField\r\n      - SlugRelatedField се използва за релации, като свързва други модели чрез техните уникални полета (например slug поле).\r\n      ```py\r\n      from rest_framework import serializers\r\n      from myapp.models import Post, Category\r\n      \r\n      class PostSerializer(serializers.ModelSerializer):\r\n          category = serializers.SlugRelatedField(slug_field='name', queryset=Category.objects.all())\r\n      \r\n          class Meta:\r\n              model = Post\r\n              fields = ['title', 'content', 'category']\r\n      ```\r\n   6. PrimaryKeyRelatedField\r\n      - Този сериалайзер използва първичния ключ за релации между модели.\r\n      ```py\r\n      from rest_framework import serializers\r\n      from myapp.models import Post, Category\r\n      \r\n      class PostSerializer(serializers.ModelSerializer):\r\n          category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all())\r\n      \r\n          class Meta:\r\n              model = Post\r\n              fields = ['title', 'content', 'category']\r\n\r\n      ```\r\n\r\n   7. StringRelatedField\r\n      - StringRelatedField показва релациите като низове, базирани на __str__() метода на свързания модел.\r\n      ```py\r\n         from rest_framework import serializers\r\n         from myapp.models import Post\r\n         \r\n         class PostSerializer(serializers.ModelSerializer):\r\n             category = serializers.StringRelatedField()\r\n         \r\n             class Meta:\r\n                 model = Post\r\n                 fields = ['title', 'content', 'category']\r\n      ```\r\n\r\n   8. SerializerMethodField\r\n      - Този тип поле ви позволява да дефинирате метод в сериалайзера, който връща данни в сериализирана форма.\r\n      ```py\r\n      from rest_framework import serializers\r\n      from myapp.models import User\r\n      \r\n      class UserSerializer(serializers.ModelSerializer):\r\n          full_name = serializers.SerializerMethodField()\r\n      \r\n          class Meta:\r\n              model = User\r\n              fields = ['username', 'email', 'full_name']\r\n      \r\n          def get_full_name(self, obj):\r\n              return f\"{obj.first_name} {obj.last_name}\"\r\n\r\n      ```\r\n\r\n   9. HiddenField\r\n      - Поле, което се използва за предаване на стойности, които не се показват в сериализацията (например стойности, които се запълват автоматично).\r\n      ```py\r\n      from rest_framework import serializers\r\n      \r\n      class CommentSerializer(serializers.ModelSerializer):\r\n          created_by = serializers.HiddenField(default=serializers.CurrentUserDefault())\r\n      \r\n          class Meta:\r\n              model = Comment\r\n              fields = ['text', 'created_by']\r\n      ```\r\n\r\n2. Generic Views\r\n   - Можем да ги комбинираме, тоест можем да имаме RetrieveDestroyView, което има get и delete\r\n\r\n3. Actions\r\n   - Шаблон за създаване на urls в REST\r\n   - Пример: Имаме книги и всяка книга може да има коментар\r\n   - Грешно: api/books/4 - където 4 е id-то на книгата\r\n   - Правилно: api/books/4/comment - това наричаме акшън, по този начин няма объркване на какво принадлежи id-то\r\n\r\n4. Authentication\r\n   - `rest_framework.authtoken`\r\n   - Апп, който съдържа едно view ObtainAuthToken\r\n     - Тук можем да генерираме токън за съществуващ наш потребител\r\n   - Login\r\n     ```py\r\n     class LoginAPIView(token_views.ObtainAuthToken)\r\n        pass\r\n     ```\r\n   - Register\r\n     ```py\r\n      from django.contrib.auth import get_user_model\r\n      from rest_framework import serializers\r\n      \r\n      UserModel = get_user_model()\r\n      \r\n      class RegisterSerializer(serializers.ModelSerializer):\r\n          password = serializers.CharField(write_only=True)  # Prevent password from being read\r\n      \r\n          class Meta:\r\n              model = UserModel\r\n              fields = ['username', 'email', 'password']  # Adjust fields based on your User model\r\n      \r\n          def create(self, validated_data):\r\n              # Use the create_user method to create a user\r\n              user = UserModel.objects.create_user(\r\n                  username=validated_data['username'],\r\n                  email=validated_data['email'],\r\n                  password=validated_data['password']  # create_user automatically handles hashing\r\n              )\r\n              return user\r\n\r\n      from django.contrib.auth import get_user_model\r\n      from rest_framework import generics\r\n      from rest_framework.response import Response\r\n      from rest_framework import status\r\n      \r\n      UserModel = get_user_model()\r\n      \r\n      class RegisterApiView(generics.CreateAPIView):\r\n          queryset = UserModel.objects.all()\r\n          serializer_class = RegisterSerializer\r\n     ```\r\n\r\n5. Permissions\r\n   - Можем да използваме mixins от django, но е по-прието да използваме permission classes\r\n      - IsAuthenticated\r\n      - AllowAny\r\n      - IsAdminUser\r\n      - IsAuthenticatedOrReadOnly\r\n      - BasePermission (for creating custom permissions)\r\n   ```py\r\n   from rest_framework.permissions import BasePermission\r\n   \r\n   class IsOwner(BasePermission):\r\n       \"\"\"\r\n       Custom permission to only allow owners of an object to access or modify it.\r\n       \"\"\"\r\n       def has_object_permission(self, request, view, obj):\r\n           # Assumes the object has an 'owner' attribute. You can adjust this as needed.\r\n           return obj.owner == request.user\r\n\r\n   class MyModelDetailView(generics.RetrieveUpdateDestroyAPIView):\r\n       queryset = MyModel.objects.all()\r\n       serializer_class = MyModelSerializer\r\n       permission_classes = [IsOwner]  # Use the custom permission\r\n   \r\n   ```\r\n    \r\n6. Exceptions\r\n   - Персонализирани грешки\r\n   ```py\r\n      class NotOwnerException(APIException):\r\n          status_code = 403  # HTTP status code for Forbidden\r\n          default_detail = \"You do not have permission to perform this action.\"  # Default error message\r\n          default_code = 'not_owner'\r\n   ```\r\n   - Custom handler\r\n   ```py\r\n   from rest_framework.views import exception_handler  # Import the default DRF exception handler\r\n   from rest_framework.response import Response  # Import DRF's Response class\r\n   from rest_framework.exceptions import APIException  # Import DRF's base API exception\r\n   from rest_framework import status  # Import status codes\r\n   \r\n   def custom_exception_handler(exc, context):\r\n       \"\"\"\r\n       Custom exception handler to modify the response for exceptions.\r\n       \r\n       Args:\r\n       - exc: The exception instance that was raised.\r\n       - context: A dictionary containing information about the context in which the exception was raised (including the view).\r\n       \r\n       Returns:\r\n       - Response: A DRF Response object that modifies the error response.\r\n       \"\"\"\r\n   \r\n       # Call DRF's default exception handler first, to get the standard error response.\r\n       response = exception_handler(exc, context)\r\n       \r\n       # If the response is not None, it means DRF has already handled the exception.\r\n       if response is not None:\r\n           # Modify the response for custom behavior (if needed)\r\n           # You can add custom data or wrap the error in a different structure.\r\n           \r\n           # Example: Adding a custom error message or structure to the response\r\n           response.data['status_code'] = response.status_code  # Add status code to the response\r\n           response.data['error_type'] = exc.__class__.__name__  # Add the type of exception to the response\r\n           \r\n           # Optionally, you could modify specific responses for certain exceptions\r\n           if isinstance(exc, APIException):\r\n               # Handle general APIException separately (e.g., add more context)\r\n               response.data['detail'] = str(exc)  # Add the exception message as 'detail'\r\n       \r\n       else:\r\n           # If the response is None, it means DRF's default handler didn't handle this exception.\r\n           # You can create a custom response here.\r\n           \r\n           # Example: Creating a custom response for unhandled exceptions\r\n           return Response({\r\n               \"detail\": \"Something went wrong\",  # Custom error message\r\n               \"error\": str(exc),  # Include the string representation of the exception\r\n               \"error_type\": exc.__class__.__name__,  # Include the type of the exception\r\n           }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)  # Return 500 status for server errors\r\n       \r\n       # Return the modified response or the original response from the default handler.\r\n       return response\r\n\r\n   // settings.py\r\n   REST_FRAMEWORK = {\r\n    'EXCEPTION_HANDLER': 'your_project.your_module.custom_exception_handler',  # Replace with your actual path\r\n   }\r\n\r\n   ```\r\n---\r\n\r\n\r\n### Async Operations\r\n\r\n1. Django по дефиниция е синхронно\r\n   - Много потребители изпращат заявка към един сървър едновременно.\r\n   - Django създава потоци, които се изпълняват последователно, за всеки един от тях\r\n   - Те могат и да се изпълняват и паралено, ако направим такава настройка на нашия уеб сървър\r\n\r\n2. Синхронно програмиране\r\n   - Действията се изпълняват едно след друго\r\n   ```py\r\n   import time\r\n\r\n   def get_milk():\r\n       print(\"Servant is going to get milk...\")\r\n       time.sleep(1)  # Simulates 1 second to get milk\r\n       print(\"Servant got the milk.\")\r\n   \r\n   def get_coffee():\r\n       print(\"Servant is going to get coffee...\")\r\n       time.sleep(1.5)  # Simulates 1.5 seconds to get coffee\r\n       print(\"Servant got the coffee.\")\r\n   \r\n   def prepare_drink():\r\n       print(\"Servant is preparing the drink...\")\r\n       time.sleep(0.5)  # Simulates 0.5 seconds to prepare the drink\r\n       print(\"Servant prepared the drink.\")\r\n   \r\n   def serve():\r\n       start_time = time.time()\r\n   \r\n       get_milk()\r\n       get_coffee()\r\n       prepare_drink()\r\n   \r\n       total_time = time.time() - start_time\r\n       print(f\"Total time taken: {total_time:.2f} seconds\")\r\n   \r\n   if __name__ == \"__main__\":\r\n       serve()\r\n   ```\r\n\r\n3. Асинхронни операции\r\n   - Действията се изпълняват едновреммено\r\n   ```py\r\n   import asyncio\r\n   import time\r\n   \r\n   async def get_milk():\r\n       print(\"Servant is going to get milk...\")\r\n       await asyncio.sleep(1)  # Simulates 1 second to get milk\r\n       print(\"Servant got the milk.\")\r\n   \r\n   async def get_coffee():\r\n       print(\"Servant is going to get coffee...\")\r\n       await asyncio.sleep(1.5)  # Simulates 1.5 seconds to get coffee\r\n       print(\"Servant got the coffee.\")\r\n   \r\n   async def prepare_drink():\r\n       print(\"Servant is preparing the drink...\")\r\n       await asyncio.sleep(0.5)  # Simulates 0.5 seconds to prepare the drink\r\n       print(\"Servant prepared the drink.\")\r\n   \r\n   async def serve():\r\n       start_time = time.time()\r\n   \r\n       # Run get_milk and get_coffee concurrently\r\n       await asyncio.gather(get_milk(), get_coffee())\r\n   \r\n       # Prepare the drink after both tasks are complete\r\n       await prepare_drink()\r\n   \r\n       total_time = time.time() - start_time\r\n       print(f\"Total time taken: {total_time:.2f} seconds\")\r\n\r\n   async def main():\r\n      await asyncio.gather(*[serve(i) for i in range 10])\r\n   \r\n   if __name__ == \"__main__\":\r\n       asyncio.run(main())\r\n```\r\n\r\n4. Celery\r\n   - Опашка от задачи\r\n   - Многопроцесорно\r\n   - Както ORM-a на Django е wrapper към всички бази, които ние можем да използваме\r\n   - Така celery e wrapper върху message-broker\r\n   - pip install celery\r\n\r\n5. Multithreading vs Multiprocessing\r\n   - Многонишковост означава, че няколко нишки (threads) работят паралелно в рамките на един и същ процес, като споделят една и съща памет, което е подходящо за задачи, свързани с вход/изход (I/O-bound). Многопроцесност означава, че се създават няколко процеса, всеки със собствена памет, което позволява истински паралелизъм и е по-подходящо за задачи, натоварващи процесора (CPU-bound).\r\n\r\n6. Redis\r\n  - In memory база от данни\r\n  - Живее в рам паметта\r\n\r\n7. SetUp\r\n   - Нов файл celery.py в нашия проект\r\n   - pip install celery[redis]\r\n\r\n   ```py\r\n   from __future__ import absolute_import, unicode_literals\r\n   import os\r\n   from celery import Celery\r\n   \r\n   # Set the default Django settings module for the 'celery' program.\r\n   os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project_name.settings')\r\n   \r\n   # Create the Celery app and configure it.\r\n   app = Celery('project_name')\r\n   \r\n   # Load the config from Django's settings file.\r\n   app.config_from_object('django.conf:settings', namespace='CELERY')\r\n   \r\n   # Auto-discover tasks from all registered Django app configs.\r\n   app.autodiscover_tasks()\r\n   \r\n   @app.task(bind=True)\r\n   def debug_task(self):\r\n       print(f'Request: {self.request!r}')\r\n\r\n   ```\r\n\r\n   - В инита на проекта\r\n   ```py\r\n   from __future__ import absolute_import, unicode_literals\r\n   \r\n   # This will make sure the app is always imported when\r\n   # Django starts so that shared_task will use this app.\r\n   from .celery import app as celery_app\r\n   \r\n   __all__ = ('celery_app',)\r\n   ```\r\n\r\n   - В настройките\r\n   ```py\r\n   # Celery settings\r\n   \r\n   # Using Redis as the broker\r\n   CELERY_BROKER_URL = 'redis://localhost:6379/0'\r\n   \r\n   # Optional configurations for Celery\r\n   CELERY_ACCEPT_CONTENT = ['json']\r\n   CELERY_TASK_SERIALIZER = 'json'\r\n   CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'  # Using Redis as the result backend\r\n   CELERY_TIMEZONE = 'UTC'\r\n   ```\r\n\r\n   - В tasks.py\r\n   ```py\r\n   from celery import shared_task\r\n   import time\r\n   \r\n   @shared_task\r\n   def add(x, y):\r\n       return x + y\r\n   \r\n   @shared_task\r\n   def sleepy_task(duration):\r\n       time.sleep(duration)\r\n       return 'Slept for {} seconds'.format(duration)\r\n\r\n   ```\r\n\r\n   - стартиране\r\n   - celery -A project_name worker --loglevel=info\r\n\r\n   - Викане на таск\r\n   - sleepy_task.delay()\r\n\r\n---\r\n\r\n---\r\n\r\n### Deployment Setup\r\n\r\n1. Guinicorn\r\n   - Не е добра идея да стартираме проекта ни в продъкшън с manage.py поради:\r\n     - Автоматично презареждане\r\n     - Грижи се за предоставянето на статични файлове (което е бавно)\r\n     - Single-threaded - Можем да имаме само една инстанция на апликацията\r\n    \r\n   - Gunicorn WSGI (Web Server Gateway Interface)\r\n     - Няма автоматично презареждане (ако изтрием файл на продъкшън няма да рестартираме сървъра\r\n     - Не предоставя статични файлове\r\n     - Можем да пуснем няколко инстанции\r\n     - Грижи се за рестартиране на работниците при проблем, следейки за тяхното изпълнение в един главен процес\r\n    \r\n   - `pip install gunicorn`\r\n   - `gunicorn [app_name].wsgi:application --workers=4 bind=0.0.0.0:8000`\r\n\r\n1.1 Uvicorn\r\n   - Използва се за стартирне на asgi\r\n   - Всеки процес е сам за себе си, тоест при евентуално спиране трябва да бъде рестартиран ръчно.\r\n   - Може да бъде комбиниран с gunicorn, за да бъде разрешен този проблем.\r\n\r\n2. Reverse Proxy (Nginx)\r\n   - Предоставя статични файлове\r\n   - Грижи се за SSL\r\n   - Пренасочва заявките между клиента и django проекта\r\n   - Serves 80, 443 ports\r\n   - Nginx е web server, които може да работи като reverse proxy\r\n   - Настройваме го от nginx.conf\r\n   - Nginx Пример без и с DOcker\r\n   - Пример с ngrok\r\n  \r\n3. Deployment Setup\r\n   - Видове среди\r\n     - Local\r\n     - Development - копие production среда, тоест не е локално, но не е това, което потребителите ползват\r\n     - Staging - Среда, на която product owner-ите да проверят дали нещата работят\r\n     - Production\r\n    \r\n   - .env файл\r\n     - Файл, в който пазим тайните на проекта ни\r\n     - os.environ.get('SECRET_KEY', '')\r\n\r\n---\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feroydev%2Fdjango-advanced","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feroydev%2Fdjango-advanced","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feroydev%2Fdjango-advanced/lists"}