تعلّم إنشاء تطبيق CRUD باستخدام Django

تعلّم إنشاء تطبيق CRUD باستخدام Django

إذا كنت تريد أن تفهم Django CRUD Application من الصفر بطريقة عملية وواضحة، فهذا المقال مكتوب لك. سنبني معًا تطبيقًا كاملًا خطوة بخطوة، ونفهم كيف يتم إنشاء البيانات، قراءتها، تعديلها، وحذفها داخل Django بأسلوب بسيط ومريح، مع أمثلة كود حقيقية يمكنك الاعتماد عليها في مشروعك القادم.

الهدف هنا ليس فقط أن تحفظ الأكواد، بل أن تفهم الفكرة وراء كل خطوة. لأنك عندما تفهم المنطق، ستستطيع بناء أي CRUD Application في Django بنفسك، سواء كان تطبيقًا لإدارة الكتب، الطلاب، المنتجات، المقالات، العملاء، أو أي نوع آخر من البيانات.

في هذا الدليل سنفترض أننا نبني تطبيقًا بسيطًا لإدارة الكتب، لكن نفس الفكرة يمكن تطبيقها على أي نموذج آخر. سنشرح كل شيء من البداية: إنشاء المشروع، إعداد التطبيق، بناء الموديل، إنشاء الفورم، كتابة الـ views، ضبط الروابط، تجهيز القوالب، ثم تنفيذ عمليات CRUD كاملة بشكل مرتب ومفهوم.


ما هو CRUD ولماذا هو مهم جدًا؟

كلمة CRUD هي اختصار لأربع عمليات أساسية في أي تطبيق يعتمد على قاعدة بيانات:

C = Create
R = Read
U = Update
D = Delete

هذه العمليات هي قلب أغلب التطبيقات التي تراها يوميًا. عندما تضيف منتجًا إلى متجر، أو تنشر منشورًا في موقع، أو تحفظ طالبًا في نظام إدارة مدرسة، فأنت عمليًا تستخدم CRUD.

في Django، تنفيذ CRUD يصبح أسهل بكثير بفضل قوة الإطار نفسه، لأنه يوفر أدوات جاهزة مثل:

  • ORM للتعامل مع قاعدة البيانات بدون كتابة SQL في أغلب الأحيان.

  • Forms لتسهيل إدخال البيانات والتحقق منها.

  • Views لتنفيذ المنطق.

  • Templates لعرض الصفحات.

  • Messages لإظهار التنبيهات للمستخدم.

  • Admin panel لإدارة البيانات بسرعة.

وهذا ما يجعل Django واحدًا من أفضل الخيارات لبناء تطبيقات الويب بسرعة وبنظافة.


ما الذي سنبنيه في هذا المقال؟

سننشئ تطبيقًا بسيطًا لإدارة الكتب، يحتوي على الحقول التالية:

  • عنوان الكتاب

  • اسم المؤلف

  • وصف الكتاب

  • تاريخ النشر

  • الحالة: متاح أو غير متاح

وسنقوم بتنفيذ العمليات التالية:

  • عرض قائمة الكتب

  • إضافة كتاب جديد

  • تعديل كتاب موجود

  • حذف كتاب

  • عرض تفاصيل كتاب

وبذلك ستحصل على CRUD متكامل من البداية إلى النهاية.


المتطلبات قبل البدء

قبل أن نبدأ، تأكد من توفر الآتي:

  • Python مثبت على جهازك

  • Django مثبت

  • محرر نصوص مثل VS Code

  • معرفة أساسية جدًا بـ Python

يمكنك تثبيت Django عبر الأمر:

pip install django

وللتأكد من التثبيت:

django-admin --version

إنشاء مشروع Django جديد

لنبدأ بإنشاء المشروع:

django-admin startproject crudproject
cd crudproject

بعد ذلك ننشئ التطبيق الخاص بنا، ولنسمه books:

python manage.py startapp books

الآن أصبح لدينا هيكل المشروع الأساسي.


إعداد التطبيق داخل settings.py

افتح ملف settings.py داخل مجلد المشروع، ثم أضف اسم التطبيق إلى INSTALLED_APPS:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'books',
]

هذا السطر مهم جدًا، لأنه يخبر Django أن التطبيق books جزء من المشروع ويجب أن يتم تحميله.


تصميم الموديل الخاص بالكتب

الخطوة التالية هي إنشاء الموديل الذي يمثل البيانات داخل قاعدة البيانات.

افتح ملف models.py داخل تطبيق books واكتب:

from django.db import models

class Book(models.Model):
    STATUS_CHOICES = [
        ('available', 'Available'),
        ('unavailable', 'Unavailable'),
    ]

    title = models.CharField(max_length=200)
    author = models.CharField(max_length=150)
    description = models.TextField()
    published_date = models.DateField()
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='available')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

ماذا يفعل هذا الموديل؟

هذا الموديل يمثل كتابًا واحدًا في قاعدة البيانات. كل سجل في جدول الكتب سيحمل هذه البيانات.

  • title لتخزين عنوان الكتاب

  • author لاسم المؤلف

  • description لوصف الكتاب

  • published_date لتاريخ النشر

  • status لحالة الكتاب

  • created_at و updated_at لمعرفة وقت الإنشاء والتعديل

الطريقة __str__ مهمة لأنها تجعل الكتاب يظهر بشكل جميل داخل Django admin أو أثناء الطباعة.


إنشاء قاعدة البيانات وترحيل الموديل

بعد إنشاء الموديل، نحتاج إلى إنشاء migration ثم تطبيقه.

python manage.py makemigrations
python manage.py migrate

الأمر الأول ينشئ ملفات الترحيل، والثاني يطبقها على قاعدة البيانات.


تسجيل الموديل داخل admin.py

حتى نتمكن من رؤية الكتب داخل لوحة الإدارة، نحتاج إلى تسجيل الموديل.

افتح ملف admin.py واكتب:

from django.contrib import admin
from .models import Book

admin.site.register(Book)

بعد ذلك أنشئ superuser:

python manage.py createsuperuser

ثم شغّل السيرفر:

python manage.py runserver

ادخل إلى:

http://127.0.0.1:8000/admin/

وسجل الدخول باستخدام بيانات المدير، وستجد نموذج Book ظاهرًا داخل لوحة الإدارة.


لماذا نستخدم ModelForm؟

في CRUD التطبيقات، من الأفضل استخدام ModelForm عندما يكون لدينا نموذج مرتبط مباشرة بالموديل. لأنه يوفر علينا كتابة الكثير من الأكواد اليدوية، كما يقوم بالتحقق من البيانات تلقائيًا.

لننشئ ملف forms.py داخل تطبيق books:

from django import forms
from .models import Book

class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = ['title', 'author', 'description', 'published_date', 'status']
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter book title'}),
            'author': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter author name'}),
            'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'placeholder': 'Enter book description'}),
            'published_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
            'status': forms.Select(attrs={'class': 'form-control'}),
        }

لماذا هذا مهم؟

  • ModelForm يربط الفورم بالموديل مباشرة.

  • fields يحدد الحقول التي نريد عرضها.

  • widgets تساعدنا في تحسين شكل الحقول داخل الصفحة.

بهذا الشكل يصبح الفورم أكثر تنظيمًا وأسهل في الاستخدام.


كتابة Views الخاصة بالـ CRUD

الآن نصل إلى قلب التطبيق. الـ views هي المسؤولة عن تنفيذ المنطق الحقيقي.

افتح views.py واكتب:

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from .models import Book
from .forms import BookForm

def book_list(request):
    books = Book.objects.all().order_by('-created_at')
    return render(request, 'books/book_list.html', {'books': books})

def book_detail(request, pk):
    book = get_object_or_404(Book, pk=pk)
    return render(request, 'books/book_detail.html', {'book': book})

def book_create(request):
    if request.method == 'POST':
        form = BookForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, 'Book created successfully.')
            return redirect('book_list')
    else:
        form = BookForm()
    return render(request, 'books/book_form.html', {'form': form, 'title': 'Add Book'})

def book_update(request, pk):
    book = get_object_or_404(Book, pk=pk)
    if request.method == 'POST':
        form = BookForm(request.POST, instance=book)
        if form.is_valid():
            form.save()
            messages.success(request, 'Book updated successfully.')
            return redirect('book_list')
    else:
        form = BookForm(instance=book)
    return render(request, 'books/book_form.html', {'form': form, 'title': 'Edit Book'})

def book_delete(request, pk):
    book = get_object_or_404(Book, pk=pk)
    if request.method == 'POST':
        book.delete()
        messages.success(request, 'Book deleted successfully.')
        return redirect('book_list')
    return render(request, 'books/book_confirm_delete.html', {'book': book})

شرح الـ views ببساطة

1) book_list

هذه الوظيفة تعرض كل الكتب الموجودة في قاعدة البيانات.
نستخدم:

Book.objects.all()

لجلب جميع السجلات، ثم نرتبها حسب وقت الإنشاء من الأحدث إلى الأقدم.

2) book_detail

تعرض تفاصيل كتاب واحد فقط.
نستخدم get_object_or_404 حتى لو لم يوجد الكتاب، سيعرض Django صفحة 404 بدلًا من حدوث خطأ غريب.

3) book_create

تتعامل مع إضافة كتاب جديد.
إذا كانت الطريقة POST فهذا يعني أن المستخدم أرسل البيانات من الفورم.
نقوم بإنشاء BookForm(request.POST)، ثم نتحقق من صحة البيانات باستخدام is_valid()، وبعدها نحفظها.

4) book_update

تشبه الإضافة، لكن الفرق أننا نمرر instance=book حتى يتم تعديل السجل الموجود بدل إنشاء سجل جديد.

5) book_delete

تعرض رسالة تأكيد قبل الحذف، ثم إذا أكد المستخدم العملية يتم حذف الكتاب.


ضبط الروابط داخل urls.py للتطبيق

الآن نحتاج إلى ربط كل View بمسار URL مناسب.

أنشئ ملف urls.py داخل تطبيق books إذا لم يكن موجودًا، ثم ضع فيه:

from django.urls import path
from . import views

urlpatterns = [
    path('', views.book_list, name='book_list'),
    path('book/<int:pk>/', views.book_detail, name='book_detail'),
    path('book/add/', views.book_create, name='book_create'),
    path('book/<int:pk>/edit/', views.book_update, name='book_update'),
    path('book/<int:pk>/delete/', views.book_delete, name='book_delete'),
]

ربط urls الخاصة بالتطبيق مع المشروع الرئيسي

افتح ملف urls.py الخاص بالمشروع الرئيسي واكتب:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('books.urls')),
]

الآن أي رابط داخل books.urls سيصبح جزءًا من الموقع الرئيسي.


إنشاء مجلد templates بشكل منظم

من الأفضل تنظيم ملفات القوالب بهذا الشكل:

books/
    templates/
        books/
            base.html
            book_list.html
            book_detail.html
            book_form.html
            book_confirm_delete.html

هذا التنظيم يساعد Django على العثور على الملفات بسهولة ويجعل المشروع مرتبًا.


إنشاء القالب الأساسي base.html

ملف base.html سيكون الأساس الذي ترث منه باقي الصفحات.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Django CRUD{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-dark bg-dark navbar-expand-lg">
        <div class="container">
            <a class="navbar-brand" href="{% url 'book_list' %}">Book Manager</a>
        </div>
    </nav>

    <div class="container mt-4">
        {% if messages %}
            {% for message in messages %}
                <div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
                    {{ message }}
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                </div>
            {% endfor %}
        {% endif %}

        {% block content %}
        {% endblock %}
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

صفحة عرض قائمة الكتب book_list.html

{% extends 'books/base.html' %}

{% block title %}Book List{% endblock %}

{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
    <h1 class="h3">Books</h1>
    <a href="{% url 'book_create' %}" class="btn btn-primary">Add New Book</a>
</div>

<div class="card shadow-sm">
    <div class="card-body">
        {% if books %}
            <div class="table-responsive">
                <table class="table table-striped align-middle">
                    <thead>
                        <tr>
                            <th>Title</th>
                            <th>Author</th>
                            <th>Status</th>
                            <th>Created</th>
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for book in books %}
                        <tr>
                            <td>{{ book.title }}</td>
                            <td>{{ book.author }}</td>
                            <td>
                                {% if book.status == 'available' %}
                                    <span class="badge bg-success">Available</span>
                                {% else %}
                                    <span class="badge bg-secondary">Unavailable</span>
                                {% endif %}
                            </td>
                            <td>{{ book.created_at|date:"Y-m-d" }}</td>
                            <td>
                                <a href="{% url 'book_detail' book.pk %}" class="btn btn-sm btn-info">View</a>
                                <a href="{% url 'book_update' book.pk %}" class="btn btn-sm btn-warning">Edit</a>
                                <a href="{% url 'book_delete' book.pk %}" class="btn btn-sm btn-danger">Delete</a>
                            </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        {% else %}
            <p class="text-muted mb-0">No books found. Start by adding your first book.</p>
        {% endif %}
    </div>
</div>
{% endblock %}

صفحة إضافة وتعديل الكتب book_form.html

هذه الصفحة سنستخدمها لكل من الإضافة والتعديل.

{% extends 'books/base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card shadow-sm">
            <div class="card-body">
                <h1 class="h4 mb-4">{{ title }}</h1>
                <form method="post">
                    {% csrf_token %}
                    {{ form.as_p }}
                    <button type="submit" class="btn btn-primary">Save</button>
                    <a href="{% url 'book_list' %}" class="btn btn-secondary">Cancel</a>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}

صفحة تفاصيل الكتاب book_detail.html

{% extends 'books/base.html' %}

{% block title %}Book Detail{% endblock %}

{% block content %}
<div class="card shadow-sm">
    <div class="card-body">
        <h1 class="h3">{{ book.title }}</h1>
        <p><strong>Author:</strong> {{ book.author }}</p>
        <p><strong>Published Date:</strong> {{ book.published_date }}</p>
        <p><strong>Status:</strong> {{ book.status }}</p>
        <p><strong>Description:</strong></p>
        <p>{{ book.description }}</p>

        <a href="{% url 'book_update' book.pk %}" class="btn btn-warning">Edit</a>
        <a href="{% url 'book_delete' book.pk %}" class="btn btn-danger">Delete</a>
        <a href="{% url 'book_list' %}" class="btn btn-secondary">Back</a>
    </div>
</div>
{% endblock %}

صفحة تأكيد الحذف book_confirm_delete.html

{% extends 'books/base.html' %}

{% block title %}Delete Book{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div class="card shadow-sm border-danger">
            <div class="card-body">
                <h1 class="h4 text-danger">Delete Book</h1>
                <p>Are you sure you want to delete <strong>{{ book.title }}</strong>?</p>

                <form method="post">
                    {% csrf_token %}
                    <button type="submit" class="btn btn-danger">Yes, Delete</button>
                    <a href="{% url 'book_list' %}" class="btn btn-secondary">Cancel</a>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}

كيف تعمل عملية Create داخل Django؟

عندما يذهب المستخدم إلى صفحة الإضافة، نعرض له فورمًا فارغًا.
بعد إدخال البيانات والضغط على Save، يتم إرسال البيانات باستخدام POST.
في الـ view نقوم بقراءة البيانات من request.POST، ثم نمررها إلى BookForm.

إذا كانت البيانات صحيحة، نحفظها في قاعدة البيانات.

مثال عملي:

def book_create(request):
    if request.method == 'POST':
        form = BookForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('book_list')
    else:
        form = BookForm()
    return render(request, 'books/book_form.html', {'form': form})

هذا هو الجوهر الحقيقي لعملية الإنشاء.


كيف تعمل عملية Read؟

القراءة هي عرض البيانات.
لدينا نوعان:

قراءة كل البيانات

books = Book.objects.all()

قراءة سجل واحد

book = get_object_or_404(Book, pk=pk)

الأولى تعرض جميع الكتب، والثانية تعرض كتابًا واحدًا فقط.


كيف تعمل عملية Update؟

عندما نريد تعديل سجل موجود، علينا أولًا جلبه من قاعدة البيانات ثم تمريره إلى الفورم.

def book_update(request, pk):
    book = get_object_or_404(Book, pk=pk)
    form = BookForm(request.POST or None, instance=book)
    if request.method == 'POST':
        if form.is_valid():
            form.save()
            return redirect('book_list')
    return render(request, 'books/book_form.html', {'form': form})

هنا instance=book هي الفكرة الأساسية، لأنها تقول Django: هذا الفورم مرتبط بكتاب موجود بالفعل، وليس بكتاب جديد.


كيف تعمل عملية Delete؟

الحذف يجب أن يتم بحذر، لذلك من الأفضل عرض صفحة تأكيد قبل تنفيذ الحذف.

def book_delete(request, pk):
    book = get_object_or_404(Book, pk=pk)
    if request.method == 'POST':
        book.delete()
        return redirect('book_list')
    return render(request, 'books/book_confirm_delete.html', {'book': book})

بهذا الشكل لن يتم حذف البيانات بمجرد فتح الرابط، بل بعد تأكيد المستخدم.


إضافة تحسينات على شكل الفورم

بدل استخدام form.as_p فقط، يمكنك عرض الحقول يدويًا لتتحكم في التصميم بشكل أفضل.

مثال:

<form method="post">
    {% csrf_token %}

    <div class="mb-3">
        <label class="form-label">Title</label>
        {{ form.title }}
        {% if form.title.errors %}
            <div class="text-danger">{{ form.title.errors }}</div>
        {% endif %}
    </div>

    <div class="mb-3">
        <label class="form-label">Author</label>
        {{ form.author }}
    </div>

    <div class="mb-3">
        <label class="form-label">Description</label>
        {{ form.description }}
    </div>

    <div class="mb-3">
        <label class="form-label">Published Date</label>
        {{ form.published_date }}
    </div>

    <div class="mb-3">
        <label class="form-label">Status</label>
        {{ form.status }}
    </div>

    <button type="submit" class="btn btn-primary">Save</button>
</form>

هذا الأسلوب يعطيك مرونة أكبر في التصميم.


إضافة CSS بسيط لتحسين الواجهة

يمكنك إنشاء ملف CSS خاص بك داخل static، أو الاكتفاء بـ Bootstrap.
لكن لو أردت إضافة لمسة بسيطة، يمكنك استخدام:

body {
    background-color: #f8f9fa;
}

.card {
    border-radius: 12px;
}

table th, table td {
    vertical-align: middle;
}

هذا سيجعل الشكل أكثر هدوءًا وراحة للعين.


التعامل مع رسائل النجاح والأخطاء

من الأشياء الجميلة في Django أنك تستطيع إظهار رسائل للمستخدم بعد كل عملية.

مثلاً:

messages.success(request, 'Book created successfully.')
messages.success(request, 'Book updated successfully.')
messages.success(request, 'Book deleted successfully.')

وهذه الرسائل تظهر في base.html داخل جزء messages.

المستخدم يحب أن يعرف ماذا حدث بعد الضغط على زر ما.
هذه التفاصيل الصغيرة تعطي شعورًا بأن التطبيق حيّ ومهني.


إضافة التحقق من صحة البيانات Validation

Django يقوم بالتحقق من صحة البيانات تلقائيًا، لكن يمكنك أيضًا إضافة تحقق مخصص.

مثال داخل forms.py:

from django import forms
from .models import Book

class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = ['title', 'author', 'description', 'published_date', 'status']

    def clean_title(self):
        title = self.cleaned_data.get('title')
        if len(title) < 3:
            raise forms.ValidationError("Title must be at least 3 characters long.")
        return title

هذا مفيد لمنع إدخال بيانات غير منطقية.


إضافة بحث بسيط داخل قائمة الكتب

من الإضافات المفيدة جدًا أن تسمح للمستخدم بالبحث عن كتاب.

يمكنك تعديل book_list هكذا:

def book_list(request):
    query = request.GET.get('q')
    books = Book.objects.all().order_by('-created_at')

    if query:
        books = books.filter(title__icontains=query)

    return render(request, 'books/book_list.html', {'books': books, 'query': query})

ثم في القالب:

<form method="get" class="mb-3">
    <div class="input-group">
        <input type="text" name="q" class="form-control" placeholder="Search by title" value="{{ query|default:'' }}">
        <button type="submit" class="btn btn-outline-primary">Search</button>
    </div>
</form>

وهذا يجعل التطبيق أكثر فائدة وواقعية.


إضافة pagination عند كثرة الكتب

إذا زادت البيانات، من الأفضل تقسيمها إلى صفحات.

from django.core.paginator import Paginator

def book_list(request):
    books = Book.objects.all().order_by('-created_at')
    paginator = Paginator(books, 5)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, 'books/book_list.html', {'page_obj': page_obj})

وفي القالب:

{% for book in page_obj %}
    <p>{{ book.title }}</p>
{% endfor %}

<div class="mt-3">
    {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}" class="btn btn-sm btn-secondary">Previous</a>
    {% endif %}

    <span class="mx-2">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>

    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}" class="btn btn-sm btn-secondary">Next</a>
    {% endif %}
</div>

استخدام Class-Based Views بدل Function-Based Views

ما شرحناه حتى الآن يعتمد على Function-Based Views، وهي ممتازة للتعلم والفهم.
لكن Django أيضًا يوفر Class-Based Views، وهي مفيدة لتقليل التكرار في الأكواد.

مثال:

from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from .models import Book
from .forms import BookForm

class BookListView(ListView):
    model = Book
    template_name = 'books/book_list.html'
    context_object_name = 'books'
    ordering = ['-created_at']

class BookDetailView(DetailView):
    model = Book
    template_name = 'books/book_detail.html'
    context_object_name = 'book'

class BookCreateView(CreateView):
    model = Book
    form_class = BookForm
    template_name = 'books/book_form.html'
    success_url = reverse_lazy('book_list')

class BookUpdateView(UpdateView):
    model = Book
    form_class = BookForm
    template_name = 'books/book_form.html'
    success_url = reverse_lazy('book_list')

class BookDeleteView(DeleteView):
    model = Book
    template_name = 'books/book_confirm_delete.html'
    success_url = reverse_lazy('book_list')

هذه الطريقة أنيقة جدًا، لكنها قد تكون أصعب قليلًا على المبتدئين مقارنة بالأسلوب الوظيفي.


أيهما أفضل: Function-Based أم Class-Based؟

لا يوجد جواب واحد صحيح للجميع.

Function-Based Views

مناسبة إذا كنت:

  • مبتدئًا

  • تريد فهم المنطق خطوة بخطوة

  • تفضل الوضوح والبساطة

Class-Based Views

مناسبة إذا كنت:

  • تعمل على مشروع كبير

  • تريد تقليل الكود المتكرر

  • تحب التنظيم والاختصار

في هذا المقال ركزنا على Function-Based Views لأنها الأسهل للفهم، ثم أضفنا نموذجًا سريعًا للـ Class-Based Views حتى تكون الصورة كاملة.


مثال كامل عملي لتطبيق CRUD بسيط

لنجمع الفكرة في مشهد واحد:

  1. المستخدم يفتح صفحة قائمة الكتب.

  2. يرى الكتب الموجودة داخل جدول.

  3. يضغط على زر Add New Book.

  4. يظهر له الفورم.

  5. يكتب البيانات ثم يحفظها.

  6. تنتقل البيانات إلى قاعدة البيانات.

  7. تظهر رسالة نجاح.

  8. يعود إلى صفحة القائمة ويشاهد الكتاب الجديد.

  9. يمكنه بعد ذلك فتح صفحة التفاصيل.

  10. أو تعديل الكتاب.

  11. أو حذفه بعد التأكيد.

هذه الدورة الكاملة هي ما يسمى CRUD Workflow.


أخطاء شائعة عند بناء CRUD في Django

عند العمل على CRUD، قد تواجه بعض الأخطاء الشائعة. من المهم معرفتها حتى لا تضيع وقتك.

1) نسيان csrf_token

إذا نسيت:

{% csrf_token %}

فإن Django سيرفض إرسال الفورم.

2) نسيان include للتطبيق في urls.py

إذا لم تضف:

path('', include('books.urls'))

فلن تعمل الروابط الخاصة بالتطبيق.

3) نسيان إضافة التطبيق في INSTALLED_APPS

إذا لم تضف books داخل الإعدادات، فقد لا يتعرف Django على الموديل أو القوالب بشكل صحيح.

4) نسيان migrations

بعد تعديل models.py يجب دائمًا تنفيذ:

python manage.py makemigrations
python manage.py migrate

5) عدم استخدام instance في التعديل

إذا نسيت:

instance=book

فإن Django قد ينشئ سجلًا جديدًا بدل تعديل السجل الحالي.


أفكار لتطوير المشروع بعد الانتهاء

بعد أن تنتهي من CRUD الأساسي، يمكنك تطوير المشروع بسهولة بإضافة:

  • رفع صورة للكتاب

  • تصنيف الكتب حسب النوع

  • ربط الكتاب بالمؤلف بشكل منفصل

  • إضافة نظام تسجيل دخول

  • تحديد من يمكنه الإضافة أو التعديل أو الحذف

  • إضافة API باستخدام Django REST Framework

  • تحسين الواجهة باستخدام Tailwind أو Bootstrap 5

  • دعم البحث المتقدم والفلاتر

  • تصدير البيانات إلى Excel أو PDF

هذه الإضافات تجعل التطبيق أقرب إلى مشروع حقيقي قابل للنشر.


نسخة محسنة من الموديل مع صورة اختيارية

إذا أردت تطوير التطبيق أكثر، يمكنك إضافة صورة للكتاب:

from django.db import models

class Book(models.Model):
    STATUS_CHOICES = [
        ('available', 'Available'),
        ('unavailable', 'Unavailable'),
    ]

    title = models.CharField(max_length=200)
    author = models.CharField(max_length=150)
    description = models.TextField()
    published_date = models.DateField()
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='available')
    cover_image = models.ImageField(upload_to='book_covers/', blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

ثم لا تنسَ إعداد media files في Django، وهو موضوع مهم جدًا إذا كنت ستتعامل مع الصور.


لماذا هذا المشروع مهم جدًا للمبتدئين؟

لأن CRUD هو الأساس.
إذا أتقنت CRUD، فأنت قطعت نصف الطريق تقريبًا في فهم Django.

هذا النوع من المشاريع يعلمك:

  • كيفية ربط الموديل بقاعدة البيانات

  • كيفية التعامل مع الفورمات

  • كيفية عرض البيانات داخل القوالب

  • كيفية استخدام الروابط

  • كيفية تنظيم الملفات

  • كيفية التفكير في بناء التطبيقات بشكل عملي

ومن الجميل أن نفس البنية تتكرر في عدد هائل من المشاريع الحقيقية، لذلك ما تتعلمه هنا لن يكون نظريًا فقط، بل سيفيدك في العمل الفعلي.


نصيحة من القلب أثناء التعلم

لا تحاول أن تحفظ كل شيء مرة واحدة.
ابدأ بفهم الفكرة، ثم اكتب الكود بيدك، ثم أضف تعديلًا صغيرًا، ثم جرّب مرة أخرى.

البرمجة ليست سباقًا، بل رحلة.
وفي كل مرة تعيد فيها بناء CRUD من جديد، ستلاحظ أنك أصبحت أسرع، وأوضح، وأكثر ثقة بنفسك. وهذا شعور جميل جدًا، خاصة عندما ترى الصفحة تعمل أمامك وتستجيب كما خططت لها.

إذا واجهتك مشكلة، لا تعتبرها فشلًا.
في الحقيقة، الأخطاء هي أفضل معلم في عالم البرمجة.
كل رسالة خطأ تقرأها وتفهمها تجعلك أقوى.


الخلاصة

بناء Django CRUD Application خطوة بخطوة ليس أمرًا صعبًا كما يبدو في البداية.
عندما تفكك المشروع إلى أجزاء صغيرة، يصبح كل شيء أوضح بكثير:

  • أنشأنا مشروع Django

  • أضفنا التطبيق

  • صنعنا الموديل

  • نفذنا migrations

  • أنشأنا ModelForm

  • كتبنا views

  • ضبطنا urls

  • أنشأنا templates

  • نفذنا Create و Read و Update و Delete

  • أضفنا رسائل وتحسينات

  • وتحدثنا عن التطوير المستقبلي

هذا هو الأساس الذي يمكنك البناء عليه في أي مشروع Django آخر.
وإذا أردت أن تتقدم أكثر، يمكنك تحويل هذا المشروع إلى نظام كامل لإدارة مكتبة أو متجر أو إدارة محتوى أو لوحة تحكم داخلية.

الجميل في Django أنه منظم، عملي، ويمنحك إحساسًا بأنك تسيطر على المشروع من البداية إلى النهاية.
ومع كل مشروع CRUD جديد، ستشعر أن الأمور أصبحت أسهل وأقرب إلى ذهنك.

#Django CRUD Tutorial #Django CRUD Application #Django Step by Step #Python Django CRUD #تعلم Django #شرح Django CRUD #Django Models #Django Forms