حسِّن بيئتك من أجل التطوير والإنتاج: برنامج تعليمي للمحيطات ، الجزء الثاني
نشرت: 2022-07-22يمكن للمطورين أن يكونوا أسوأ أعدائهم. لقد رأيت أمثلة لا حصر لها من المهندسين الذين يطورون على نظام لا يتناسب مع بيئة الإنتاج الخاصة بهم. يؤدي هذا التنافر إلى عمل إضافي وعدم اكتشاف أخطاء النظام حتى وقت لاحق في عملية التطوير. ستؤدي محاذاة هذه الإعدادات في النهاية إلى تسهيل عمليات النشر المستمرة. مع وضع ذلك في الاعتبار ، سننشئ تطبيقًا نموذجيًا على بيئة تطوير Django ، مبسطًا من خلال Docker و pydantic و conda.
تستخدم بيئة التطوير النموذجية:
- مستودع محلي
- قاعدة بيانات PostgreSQL مستندة إلى Docker ؛ و
- بيئة كوندا (لإدارة تبعيات بايثون).
Pydantic و Django مناسبان للمشاريع البسيطة والمعقدة. تعرض الخطوات التالية حلاً بسيطًا يسلط الضوء على كيفية عكس بيئاتنا.
تكوين مستودع بوابة
قبل أن نبدأ في كتابة التعليمات البرمجية أو تثبيت أنظمة التطوير ، فلنقم بإنشاء مستودع Git محلي:
mkdir hello-visitor cd hello-visitor git init
سنبدأ بملف Python .gitignore
الأساسي في جذر المستودع. خلال هذا البرنامج التعليمي ، سنضيف إلى هذا الملف قبل إضافة الملفات التي لا نريد أن يتتبعها Git.
تكوين Django PostgreSQL باستخدام Docker
يتطلب Django قاعدة بيانات علائقية ويستخدم SQLite افتراضيًا. عادة ما نتجنب SQLite لتخزين البيانات ذات المهام الحرجة لأنه لا يتعامل مع وصول المستخدم المتزامن بشكل جيد. يختار معظم المطورين قاعدة بيانات إنتاج نموذجية ، مثل PostgreSQL. بغض النظر ، يجب أن نستخدم نفس قاعدة البيانات للتطوير والإنتاج. هذا التفويض المعماري جزء من تطبيق The Twelve-Factor App.
لحسن الحظ ، فإن تشغيل مثيل PostgreSQL محلي باستخدام Docker و Docker Compose يعد أمرًا سهلاً.
لتجنب تلويث الدليل الجذر الخاص بنا ، سنضع الملفات المتعلقة بـ Docker في أدلة فرعية منفصلة. سنبدأ بإنشاء ملف Docker Compose لنشر PostgreSQL:
# docker-services/docker-compose.yml version: "3.9" services: db: image: "postgres:13.4" env_file: .env volumes: - hello-visitor-postgres:/var/lib/postgresql/data ports: - ${POSTGRES_PORT}:5432 volumes: hello-visitor-postgres:
بعد ذلك ، سننشئ ملف بيئة docker-compose
لتهيئة حاوية PostgreSQL الخاصة بنا:
# docker-services/.env POSTGRES_USER=postgres POSTGRES_PASSWORD=MyDBPassword123 # The 'maintenance' database POSTGRES_DB=postgres # The port exposed to localhost POSTGRES_PORT=5432
تم تعريف خادم قاعدة البيانات وتكوينه الآن. لنبدأ الحاوية الخاصة بنا في الخلفية:
sudo docker compose --project-directory docker-services/ up -d
من المهم ملاحظة استخدام sudo في الأمر السابق. سيكون مطلوبًا ما لم يتم اتباع خطوات محددة في بيئة التطوير لدينا.
إنشاء قاعدة بيانات
دعنا نتصل بـ PostgreSQL ونهيئها باستخدام مجموعة أدوات قياسية ، pgAdmin4. سنستخدم نفس بيانات اعتماد تسجيل الدخول التي تم تكوينها مسبقًا في متغيرات البيئة.
لنقم الآن بإنشاء قاعدة بيانات جديدة باسم hello_visitor
:
مع وجود قاعدة البيانات الخاصة بنا ، نحن جاهزون لتثبيت بيئة البرمجة الخاصة بنا.
إدارة بيئة Python عبر Miniconda
نحتاج الآن إلى إعداد بيئة بايثون معزولة والاعتماديات المطلوبة. لتبسيط الإعداد والصيانة ، اخترنا Miniconda.
لنقم بإنشاء بيئة كوندا الخاصة بنا ونفعّلها:
conda create --name hello-visitor python=3.9 conda activate hello-visitor
الآن ، سننشئ ملفًا ، hello-visitor/requirements.txt
، مع تعداد تبعيات Python الخاصة بنا:
django # PostgreSQL database adapter: psycopg2 # Pushes .env key-value pairs into environment variables: python-dotenv pydantic # Utility library to read database connection information: dj-database-url # Static file caching: whitenoise # Python WSGI HTTP Server: gunicorn
بعد ذلك ، سنطلب من Python تثبيت هذه التبعيات:
cd hello-visitor pip install -r requirements.txt
يجب الآن تثبيت تبعياتنا استعدادًا لأعمال تطوير التطبيق.
سقالات جانغو
سنعمل على دعم مشروعنا وتطبيقنا من خلال تشغيل django-admin
أولاً ، ثم تشغيل ملف يقوم بإنشائه ، manage.py
:
# From the `hello-visitor` directory mkdir src cd src # Generate starter code for our Django project. django-admin startproject hello_visitor . # Generate starter code for our Django app. python manage.py startapp homepage
بعد ذلك ، نحتاج إلى تكوين Django لتحميل مشروعنا. يتطلب ملف settings.py
تعديلًا في مجموعة INSTALLED_APPS
لتسجيل تطبيق homepage
الذي تم إنشاؤه حديثًا:
# src/hello_visitor/settings.py # ... INSTALLED_APPS = [ "homepage.apps.HomepageConfig", "django.contrib.admin", # ... ] # ...
تكوين إعداد التطبيق
باستخدام نهج إعدادات pydantic و Django الموضح في الدفعة الأولى ، نحتاج إلى إنشاء ملف متغيرات البيئة لنظام التطوير لدينا. سننقل إعداداتنا الحالية إلى هذا الملف على النحو التالي:
- قم بإنشاء ملف
src/.env
للاحتفاظ بإعدادات بيئة التطوير الخاصة بنا. - انسخ الإعدادات من
src/hello_visitor/settings.py
وأضفها إلىsrc/.env
. - قم بإزالة تلك الأسطر المنسوخة من ملف
settings.py
. - تأكد من أن سلسلة اتصال قاعدة البيانات تستخدم نفس بيانات الاعتماد التي قمنا بتكوينها مسبقًا.
يجب أن يبدو ملف البيئة الخاص بنا ، src/.env
، على النحو التالي:
DATABASE_URL=postgres://postgres:MyDBPassword123@localhost:5432/hello_visitor DATABASE_SSL=False SECRET_KEY="django-insecure-sackl&7(1hc3+%#*4e=)^q3qiw!hnnui*-^($o8t@2^^qqs=%i" DEBUG=True DEBUG_TEMPLATES=True USE_SSL=False ALLOWED_HOSTS='[ "localhost", "127.0.0.1", "0.0.0.0" ]'
سنقوم بتهيئة Django لقراءة الإعدادات من متغيرات بيئتنا باستخدام pydantic ، مع مقتطف الشفرة هذا:
# src/hello_visitor/settings.py import os from pathlib import Path from pydantic import ( BaseSettings, PostgresDsn, EmailStr, HttpUrl, ) import dj_database_url # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent class SettingsFromEnvironment(BaseSettings): """Defines environment variables with their types and optional defaults""" # PostgreSQL DATABASE_URL: PostgresDsn DATABASE_SSL: bool = True # Django SECRET_KEY: str DEBUG: bool = False DEBUG_TEMPLATES: bool = False USE_SSL: bool = False ALLOWED_HOSTS: list class Config: """Defines configuration for pydantic environment loading""" env_file = str(BASE_DIR / ".env") case_sensitive = True config = SettingsFromEnvironment() os.environ["DATABASE_URL"] = config.DATABASE_URL DATABASES = { "default": dj_database_url.config(conn_max_age=600, ssl_require=config.DATABASE_SSL) } SECRET_KEY = config.SECRET_KEY DEBUG = config.DEBUG DEBUG_TEMPLATES = config.DEBUG_TEMPLATES USE_SSL = config.USE_SSL ALLOWED_HOSTS = config.ALLOWED_HOSTS # ...
إذا واجهتك أية مشكلات بعد إكمال التعديلات السابقة ، فقارن ملف settings.py
المصمم لدينا بالإصدار الموجود في مستودع الكود المصدري الخاص بنا.
إنشاء النموذج
يتتبع تطبيقنا ويعرض عدد زوار الصفحة الرئيسية. نحتاج إلى نموذج للاحتفاظ بهذا العدد ثم استخدام مخطط ربط الكائنات (ORM) من Django لتهيئة صف قاعدة بيانات واحد عبر ترحيل البيانات.
أولاً ، سننشئ نموذج VisitCounter
الخاص بنا:
# hello-visitor/src/homepage/models.py """Defines the models""" from django.db import models class VisitCounter(models.Model): """ORM for VisitCounter""" count = models.IntegerField() @staticmethod def insert_visit_counter(): """Populates database with one visit counter. Call from a data migration.""" visit_counter = VisitCounter(count=0) visit_counter.save() def __str__(self): return f"VisitCounter - number of visits: {self.count}"
بعد ذلك ، سنقوم بتشغيل الترحيل لإنشاء جداول قاعدة البيانات الخاصة بنا:
# in the `src` folder python manage.py makemigrations python manage.py migrate
للتحقق من وجود جدول homepage_visitcounter
، يمكننا عرض قاعدة البيانات في pgAdmin4.
بعد ذلك ، نحتاج إلى وضع قيمة أولية في جدول homepage_visitcounter
. لنقم بإنشاء ملف ترحيل منفصل لإنجاز ذلك باستخدام سقالات Django:
# from the 'src' directory python manage.py makemigrations --empty homepage
سنقوم بتعديل ملف الترحيل الذي تم إنشاؤه لاستخدام طريقة VisitCounter.insert_visit_counter
التي حددناها في بداية هذا القسم:
# src/homepage/migrations/0002_auto_-------_----.py # Note: The dashes are dependent on execution time. from django.db import migrations from ..models import VisitCounter def insert_default_items(apps, _schema_editor): """Populates database with one visit counter.""" # To learn about apps, see: # https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations VisitCounter.insert_visit_counter() class Migration(migrations.Migration): """Runs a data migration.""" dependencies = [ ("homepage", "0001_initial"), ] operations = [ migrations.RunPython(insert_default_items), ]
نحن الآن جاهزون لتنفيذ هذا الترحيل المعدل لتطبيق homepage
:
# from the 'src' directory python manage.py migrate homepage
دعنا نتحقق من تنفيذ الترحيل بشكل صحيح من خلال الاطلاع على محتويات جدولنا:
نرى أن جدول homepage_visitcounter
الخاص بنا موجود وقد تم ملؤه بعدد زيارات أولي يساوي 0. مع تربيع قاعدة البيانات الخاصة بنا ، سنركز على إنشاء واجهة المستخدم الخاصة بنا.
إنشاء وتكوين وجهات نظرنا
نحتاج إلى تنفيذ جزأين رئيسيين من واجهة المستخدم الخاصة بنا: طريقة عرض ونموذج.
نقوم بإنشاء عرض homepage
لزيادة عدد الزوار ، وحفظه في قاعدة البيانات ، وتمرير هذا العدد إلى القالب للعرض:
# src/homepage/views.py from django.shortcuts import get_object_or_404, render from .models import VisitCounter def index(request): """View for the main page of the app.""" visit_counter = get_object_or_404(VisitCounter, pk=1) visit_counter.count += 1 visit_counter.save() context = {"visit_counter": visit_counter} return render(request, "homepage/index.html", context)
يحتاج تطبيق Django إلى الاستماع إلى الطلبات الموجهة إلى homepage
. لتهيئة هذا الإعداد ، سنضيف هذا الملف:
# src/homepage/urls.py """Defines urls""" from django.urls import path from . import views # The namespace of the apps' URLconf app_name = "homepage" # pylint: disable=invalid-name urlpatterns = [ path("", views.index, name="index"), ]
لكي يتم تقديم تطبيق homepage
لدينا ، يجب علينا تسجيله في ملف urls.py
مختلف:
# src/hello_visitor/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path("", include("homepage.urls")), path("admin/", admin.site.urls), ]
سيعيش قالب HTML الأساسي لمشروعنا في ملف جديد ، src/templates/layouts/base.html
:
<!DOCTYPE html> {% load static %} <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous"> <title>Hello, visitor!</title> <link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/> </head> <body> {% block main %}{% endblock %} <!-- Option 1: Bootstrap Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script> </body> </html>
سنقوم بتوسيع النموذج الأساسي لتطبيق صفحتنا homepage
في ملف جديد ، src/templates/homepage/index.html
:
{% extends "layouts/base.html" %} {% block main %} <main> <div class="container py-4"> <div class="p-5 mb-4 bg-dark text-white text-center rounded-3"> <div class="container-fluid py-5"> <h1 class="display-5 fw-bold">Hello, visitor {{ visit_counter.count }}!</h1> </div> </div> </div> </main> {% endblock %}
الخطوة الأخيرة في إنشاء واجهة المستخدم الخاصة بنا هي إخبار Django بمكان العثور على هذه القوالب. دعنا نضيف عنصر قاموس TEMPLATES['DIRS']
إلى ملف settings.py
الخاص بنا:
# src/hello_visitor/settings.py TEMPLATES = [ { ... 'DIRS': [BASE_DIR / 'templates'], ... }, ]
تم الآن تنفيذ واجهة المستخدم الخاصة بنا ونحن جاهزون تقريبًا لاختبار وظائف تطبيقنا. قبل أن نجري اختباراتنا ، نحتاج إلى وضع الجزء الأخير من بيئتنا في مكانه: التخزين المؤقت للمحتوى الثابت.
تكوين المحتوى الثابت لدينا
لتجنب استخدام الاختصارات المعمارية في نظام التطوير لدينا ، سنقوم بتكوين التخزين المؤقت للمحتوى الثابت ليعكس بيئة الإنتاج لدينا.
سنحتفظ بجميع الملفات الثابتة الخاصة بمشروعنا في دليل واحد ، src/static
، ونوجه Django لتجميع هذه الملفات قبل النشر.
سنستخدم شعار Toptal للأيقونة المفضلة favicon
ونخزنه كـ src/static/favicon.ico
:
# from `src` folder mkdir static cd static wget https://frontier-assets.toptal.com/83b2f6e0d02cdb3d951a75bd07ee4058.png mv 83b2f6e0d02cdb3d951a75bd07ee4058.png favicon.ico
بعد ذلك ، سنقوم بتهيئة Django لتجميع الملفات الثابتة:
# src/hello_visitor/settings.py # Static files (CSS, JavaScript, images) # a la https://docs.djangoproject.com/en/3.2/howto/static-files/ # # Source location where we'll store our static files STATICFILES_DIRS = [BASE_DIR / "static"] # Build output location where Django collects all static files STATIC_ROOT = BASE_DIR / "staticfiles" STATIC_ROOT.mkdir(exist_ok=True) # URL to use when referring to static files located in STATIC_ROOT. STATIC_URL = "/static/" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
نريد فقط تخزين ملفاتنا الثابتة الأصلية في مستودع الكود المصدري ؛ لا نريد تخزين الإصدارات المحسّنة للإنتاج. دعنا نضيف الأخير إلى .gitignore
الخاص بنا بهذا السطر البسيط:
staticfiles
من خلال مخزن الكود المصدري الخاص بنا الذي يقوم بتخزين الملفات المطلوبة بشكل صحيح ، نحتاج الآن إلى تكوين نظام التخزين المؤقت لدينا للعمل مع هذه الملفات الثابتة.
التخزين المؤقت للملفات الثابتة
في الإنتاج - وبالتالي في بيئة التطوير أيضًا - سنستخدم WhiteNoise لخدمة الملفات الثابتة لتطبيق Django بشكل أكثر كفاءة.
نقوم بتسجيل WhiteNoise كبرنامج وسيط عن طريق إضافة المقتطف التالي إلى ملف src/hello_visitor/settings.py
. تم تحديد أمر التسجيل بدقة ، ويجب أن تظهر WhiteNoiseMiddleware
مباشرة بعد SecurityMiddleware
:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # ... ] STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
يجب الآن تكوين التخزين المؤقت للملفات الثابتة في بيئة التطوير الخاصة بنا ، مما يمكننا من تشغيل تطبيقنا.
تشغيل خادم التطوير الخاص بنا
لدينا تطبيق مشفر بالكامل ويمكننا الآن تشغيل خادم الويب الخاص بالتطوير المضمن لـ Django باستخدام هذا الأمر:
# in the `src` folder python manage.py runserver
عندما ننتقل إلى http://localhost:8000
، سيزداد العدد في كل مرة نقوم فيها بتحديث الصفحة:
لدينا الآن تطبيق عملي سيزيد عدد زياراته أثناء قيامنا بتحديث الصفحة.
جاهز للنشر
لقد غطى هذا البرنامج التعليمي جميع الخطوات اللازمة لإنشاء تطبيق عملي في بيئة تطوير Django جميلة تتوافق مع الإنتاج. في الجزء 3 ، سنغطي نشر تطبيقنا في بيئة الإنتاج الخاصة به. من الجدير أيضًا استكشاف تماريننا الإضافية التي تسلط الضوء على فوائد Django و pydantic: تم تضمينهما في مستودع التعليمات البرمجية الكامل لهذا البرنامج التعليمي pydantic.
تعرب مدونة Toptal Engineering عن امتنانها لستيفن ديفيدسون لمراجعتها واختبارها التجريبي لعينات الكود المقدمة في هذه المقالة.