Django

⌘K
  1. Home
  2. Django
  3. Beginner Crud এপ্লিকেশন
  4. advance crud

advance crud

Thought for 5 seconds### Django CRUD View Class – বিস্তারিত ব্যাখ্যা

আপনার BaseCrudView ক্লাসটি একটি সম্পূর্ণ CRUD (Create, Read, Update, Delete) অপারেশন সহ প্রিন্ট এবং ডিটেইলস ভিউ সমর্থন করে। নিচে প্রতিটি অংশের বিস্তারিত ব্যাখ্যা দেওয়া হলো:

class BaseCrudView(View):
    model = None
    form_class = None
    template_name = 'common/premium-form.html'
    list_template_name = None
    print_template_name = 'common/print.html'
    base_list_template = 'common/base-list-modern.html'
    success_url = None
    list_url = None
    title = None
    subtitle = None
    paginate_by = 10
    search_fields = ['name']

বাংলা ব্যাখ্যা: এখানে ক্লাসের প্রাথমিক কনফিগারেশন ভেরিয়েবলগুলো সেট করা হয়েছে। এই ভেরিয়েবলগুলো ভিউ ক্লাসের আচরণ নিয়ন্ত্রণ করে। যেমন:

  • model: কোন মডেল নিয়ে কাজ করবে
  • form_class: কোন ফর্ম ব্যবহার করবে
  • template_name: ফর্ম দেখানোর জন্য টেমপ্লেট
  • list_template_name: লিস্ট দেখানোর জন্য টেমপ্লেট
  • paginate_by: প্রতি পেজে কতগুলো আইটেম দেখাবে
  • search_fields: কোন ফিল্ডগুলোতে সার্চ করা যাবে
def dispatch(self, request, *args, **kwargs):
    self.action = kwargs.pop('action', 'list')
    return super().dispatch(request, *args, **kwargs)

বাংলা ব্যাখ্যা: dispatch মেথডটি সব রিকোয়েস্ট প্রথমে এখানে আসে। এখানে URL থেকে ‘action’ প্যারামিটার নেওয়া হয় (যেমন: list, create, update ইত্যাদি)। যদি কোন action না থাকে, তাহলে ডিফল্ট ‘list’ সেট করা হয়। এরপর মূল dispatch মেথড কল করা হয়।

def get(self, request, *args, **kwargs):
    if self.action == 'list':
        return self.list(request)
    elif self.action == 'create':
        return self.create_form(request)
    elif self.action == 'detail':
        return self.detail(request, kwargs.get('pk'))
    elif self.action == 'update':
        return self.update_form(request, kwargs.get('pk'))
    elif self.action == 'delete':
        return self.delete_confirm(request, kwargs.get('pk'))
    elif self.action == 'print':
        return self.print_view(request)
    return redirect(self.list_url)

বাংলা ব্যাখ্যা: GET রিকোয়েস্ট হ্যান্ডেল করার জন্য এই মেথড। action এর উপর ভিত্তি করে সঠিক মেথড কল করা হয়:

  • ‘list’ হলে তালিকা দেখানো হয়
  • ‘create’ হলে নতুন আইটেম তৈরির ফর্ম দেখানো হয়
  • ‘detail’ হলে একটি নির্দিষ্ট আইটেমের বিস্তারিত তথ্য দেখানো হয়
  • ‘update’ হলে আপডেট ফর্ম দেখানো হয়
  • ‘delete’ হলে ডিলিট কনফার্মেশন দেখানো হয়
  • ‘print’ হলে প্রিন্ট ভিউ দেখানো হয়
  • অন্য কোন action হলে লিস্ট পেজে রিডাইরেক্ট করা হয়
def post(self, request, *args, **kwargs):
    if self.action == 'create':
        return self.create_object(request)
    elif self.action == 'update':
        return self.update_object(request, kwargs.get('pk'))
    elif self.action == 'delete':
        return self.delete_object(request, kwargs.get('pk'))
    return redirect(self.list_url)

বাংলা ব্যাখ্যা: POST রিকোয়েস্ট হ্যান্ডেল করার জন্য এই মেথড। ফর্ম সাবমিট হলে এই মেথড কল হয়:

  • ‘create’ হলে নতুন অবজেক্ট তৈরি করা হয়
  • ‘update’ হলে বিদ্যমান অবজেক্ট আপডেট করা হয়
  • ‘delete’ হলে অবজেক্ট ডিলিট করা হয়
  • অন্য কোন action হলে লিস্ট পেজে রিডাইরেক্ট করা হয়
def get_search_query(self, request):
    search_query = request.GET.get('search', '')
    if not search_query:
        return None
    q_objects = Q()
    for field in self.search_fields:
        q_objects |= Q(**{f"{field}__icontains": search_query})
    return q_objects

বাংলা ব্যাখ্যা: সার্চ কোয়েরি তৈরি করার মেথড। URL থেকে ‘search’ প্যারামিটার নিয়ে Django Q অবজেক্ট তৈরি করে। search_fields এ যে ফিল্ডগুলো দেওয়া আছে, সেগুলোতে case-insensitive সার্চ করার জন্য icontains ব্যবহার করা হয়। |= অপারেটর দিয়ে OR কন্ডিশন তৈরি করা হয়, অর্থাৎ যেকোনো একটি ফিল্ডে সার্চ টার্ম পাওয়া গেলেই রেজাল্ট আসবে।

def list(self, request):
    objects = self.model.objects.all()
    search_query = self.get_search_query(request)
    if search_query:
        objects = objects.filter(search_query)
    objects = self.apply_filters(request, objects)
    paginator = Paginator(objects, self.paginate_by)
    page = request.GET.get('page')
    try:
        objects = paginator.page(page)
    except PageNotAnInteger:
        objects = paginator.page(1)
    except EmptyPage:
        objects = paginator.page(paginator.num_pages)
    context = self.get_list_context(request, objects, paginator)
    return render(request, self.list_template_name, context)

বাংলা ব্যাখ্যা: তালিকা দেখানোর মেথড। এখানে:

  1. প্রথমে সব অবজেক্ট নেওয়া হয়
  2. সার্চ কোয়েরি থাকলে সেটা অ্যাপ্লাই করা হয়
  3. অতিরিক্ত ফিল্টার অ্যাপ্লাই করা হয় (apply_filters মেথড দিয়ে)
  4. পেজিনেশন করা হয় – প্রতি পেজে paginate_by সংখ্যক আইটেম দেখানো হয়
  5. URL থেকে পেজ নম্বর নেওয়া হয়, ভুল হলে প্রথম পেজ দেখানো হয়
  6. কনটেক্সট তৈরি করে টেমপ্লেট রেন্ডার করা হয়
def apply_filters(self, request, queryset):
    return queryset

বাংলা ব্যাখ্যা: অতিরিক্ত ফিল্টার অ্যাপ্লাই করার জন্য এই মেথড। এটি সাবক্লাসে ওভাররাইড করে বিভিন্ন ফিল্টার যোগ করা যাবে। ডিফল্টভাবে কোন ফিল্টার অ্যাপ্লাই করা হয় না।

def get_list_context(self, request, objects, paginator):
    return {
        'objects': objects,
        'model_name': self.model.__name__,
        'title': f"{self.title} List",
        'create_url': f"{self.list_url}create/",
        'list_url': self.list_url,
        'print_url': f"{self.list_url}print/",
        'is_paginated': objects.has_other_pages(),
        'paginator': paginator,
        'page_obj': objects,
        'request': request,
        'base_template': self.base_list_template,
    }

বাংলা ব্যাখ্যা: লিস্ট পেজের জন্য কনটেক্সট তৈরি করার মেথড। এখানে টেমপ্লেটে ব্যবহারের জন্য বিভিন্ন ভেরিয়েবল পাঠানো হয়:

  • অবজেক্টের তালিকা
  • মডেলের নাম
  • পেজের টাইটেল
  • বিভিন্ন URL (create, list, print)
  • পেজিনেশন সম্পর্কিত তথ্য
  • বেস টেমপ্লেট
def detail(self, request, pk):
    obj = get_object_or_404(self.model, pk=pk)
    form = self.form_class(instance=obj)
    context = {
        'form': form,
        'object': obj,
        'model_name': self.model.__name__,
        'title': f"View {self.title}",
        'subtitle': f"Details for {self.title.lower()}",
        'update_url': f"{self.list_url}{pk}/update/",
        'delete_url': f"{self.list_url}{pk}/delete/",
        'cancel_url': self.list_url,
        'is_detail_view': True,
        'is_disabled': True,
    }
    return render(request, self.template_name, context)

বাংলা ব্যাখ্যা: একটি নির্দিষ্ট অবজেক্টের বিস্তারিত তথ্য দেখানোর মেথড। এখানে:

  1. প্রথমে ID দিয়ে অবজেক্ট খোঁজা হয়, না পাওয়া গেলে 404 এরর দেখানো হয়
  2. অবজেক্টের তথ্য দিয়ে ফর্ম তৈরি করা হয়
  3. কনটেক্সটে অবজেক্ট, ফর্ম, টাইটেল, সাবটাইটেল, আপডেট/ডিলিট URL ইত্যাদি পাঠানো হয়
  4. is_detail_view এবং is_disabled সেট করা হয় যাতে টেমপ্লেটে ফর্ম ফিল্ডগুলো শুধু দেখানো হয়, এডিট করা না যায়
def create_form(self, request):
    form = self.form_class()
    context = {
        'form': form,
        'title': f"Create {self.title}",
        'subtitle': f"Add a new {self.title.lower()}",
        'submit_text': 'Create',
        'cancel_url': self.list_url,
    }
    return render(request, self.template_name, context)

বাংলা ব্যাখ্যা: নতুন অবজেক্ট তৈরির ফর্ম দেখানোর মেথড। এখানে:

  1. খালি ফর্ম তৈরি করা হয়
  2. কনটেক্সটে ফর্ম, টাইটেল, সাবটাইটেল, সাবমিট বাটনের টেক্সট, ক্যানসেল URL পাঠানো হয়
  3. টেমপ্লেট রেন্ডার করা হয়
def create_object(self, request):
    form = self.form_class(request.POST, request.FILES)
    if form.is_valid():
        obj = form.save()
        messages.success(request, f"{self.title} created successfully.")
        return redirect(self.success_url)
    context = {
        'form': form,
        'title': f"Create {self.title}",
        'subtitle': f"Add a new {self.title.lower()}",
        'submit_text': 'Create',
        'cancel_url': self.list_url,
    }
    return render(request, self.template_name, context)

বাংলা ব্যাখ্যা: নতুন অবজেক্ট তৈরি করার মেথড। এখানে:

  1. POST ডাটা এবং আপলোড করা ফাইল দিয়ে ফর্ম তৈরি করা হয়
  2. ফর্ম ভ্যালিড হলে:
  3. অবজেক্ট সেভ করা হয়
  4. সাকসেস মেসেজ দেখানো হয়
  5. সাকসেস URL-এ রিডাইরেক্ট করা হয়
  6. ফর্ম ভ্যালিড না হলে:
  7. এরর মেসেজসহ ফর্ম আবার দেখানো হয়
def update_form(self, request, pk):
    obj = get_object_or_404(self.model, pk=pk)
    form = self.form_class(instance=obj)
    context = {
        'form': form,
        'title': f"Update {self.title}",
        'subtitle': f"Modify {self.title.lower()} details",
        'submit_text': 'Update',
        'cancel_url': self.list_url,
    }
    return render(request, self.template_name, context)

বাংলা ব্যাখ্যা: অবজেক্ট আপডেট করার ফর্ম দেখানোর মেথড। এখানে:

  1. ID দিয়ে অবজেক্ট খোঁজা হয়
  2. অবজেক্টের তথ্য দিয়ে ফর্ম তৈরি করা হয়
  3. কনটেক্সটে ফর্ম, টাইটেল, সাবটাইটেল, সাবমিট বাটনের টেক্সট, ক্যানসেল URL পাঠানো হয়
  4. টেমপ্লেট রেন্ডার করা হয়
def update_object(self, request, pk):
    obj = get_object_or_404(self.model, pk=pk)
    form = self.form_class(request.POST, request.FILES, instance=obj)
    if form.is_valid():
        form.save()
        messages.success(request, f"{self.title} updated successfully.")
        return redirect(self.success_url)
    context = {
        'form': form,
        'title': f"Update {self.title}",
        'subtitle': f"Modify {self.title.lower()} details",
        'submit_text': 'Update',
        'cancel_url': self.list_url,
    }
    return render(request, self.template_name, context)

বাংলা ব্যাখ্যা: অবজেক্ট আপডেট করার মেথড। এখানে:

  1. ID দিয়ে অবজেক্ট খোঁজা হয়
  2. POST ডাটা, আপলোড করা ফাইল এবং বিদ্যমান অবজেক্ট দিয়ে ফর্ম তৈরি করা হয়
  3. ফর্ম ভ্যালিড হলে:
  4. অবজেক্ট সেভ করা হয়
  5. সাকসেস মেসেজ দেখানো হয়
  6. সাকসেস URL-এ রিডাইরেক্ট করা হয়
  7. ফর্ম ভ্যালিড না হলে:
  8. এরর মেসেজসহ ফর্ম আবার দেখানো হয়
def delete_confirm(self, request, pk):
    obj = get_object_or_404(self.model, pk=pk)
    context = {
        'object': obj,
        'title': f"Delete {self.title}",
        'subtitle': f"Are you sure you want to delete this {self.title.lower()}?",
        'cancel_url': self.list_url,
    }
    return render(request, 'delete_confirm.html', context)

বাংলা ব্যাখ্যা: ডিলিট কনফার্মেশন পেজ দেখানোর মেথড। এখানে:

  1. ID দিয়ে অবজেক্ট খোঁজা হয়
  2. কনটেক্সটে অবজেক্ট, টাইটেল, সাবটাইটেল (কনফার্মেশন মেসেজ), ক্যানসেল URL পাঠানো হয়
  3. ডিলিট কনফার্মেশন টেমপ্লেট রেন্ডার করা হয়
def delete_object(self, request, pk):
    obj = get_object_or_404(self.model, pk=pk)
    obj.delete()
    messages.success(request, f"{self.title} deleted successfully.")
    return redirect(self.list_url)

বাংলা ব্যাখ্যা: অবজেক্ট ডিলিট করার মেথড। এখানে:

  1. ID দিয়ে অবজেক্ট খোঁজা হয়
  2. অবজেক্ট ডিলিট করা হয়
  3. সাকসেস মেসেজ দেখানো হয়
  4. লিস্ট পেজে রিডাইরেক্ট করা হয়
def print_view(self, request):
    object_id = request.GET.get('id')
    if object_id:
        objects = self.model.objects.filter(pk=object_id)
    else:
        objects = self.model.objects.all()
    context = {
        'objects': objects,
        'model_name': self.model.__name__,
        'title': f"{self.title} Print View",
        'list_url': self.list_url,
    }
    return render(request, self.print_template_name, context)

বাংলা ব্যাখ্যা: প্রিন্ট ভিউ দেখানোর মেথড। এখানে:

  1. URL থেকে ‘id’ প্যারামিটার নেওয়া হয়
  2. id থাকলে শুধু সেই অবজেক্ট নেওয়া হয়, না থাকলে সব অবজেক্ট নেওয়া হয়
  3. কনটেক্সটে অবজেক্ট, মডেলের নাম, টাইটেল, লিস্ট URL পাঠানো হয়
  4. প্রিন্ট টেমপ্লেট রেন্ডার করা হয়

ব্যবহার পদ্ধতি

এই BaseCrudView ক্লাস ব্যবহার করে আপনি সহজেই যেকোনো মডেলের জন্য CRUD অপারেশন ইমপ্লিমেন্ট করতে পারবেন। উদাহরণস্বরূপ:

class ProductView(BaseCrudView):
    model = Product
    form_class = ProductForm
    list_template_name = 'products/product_list.html'
    success_url = reverse_lazy('product-list')
    list_url = reverse_lazy('product-list')
    title = 'Product'
    search_fields = ['name', 'code', 'category__name']

    def apply_filters(self, request, queryset):
        category_id = request.GET.get('category')
        if category_id:
            queryset = queryset.filter(category_id=category_id)
        return queryset

URL কনফিগারেশন

URL কনফিগারেশন এভাবে করতে পারেন:

urlpatterns = [
    path('products/', ProductView.as_view(), name='product-list'),
    path('products/create/', ProductView.as_view(action='create'), name='product-create'),
    path('products/<int:pk>/', ProductView.as_view(action='detail'), name='product-detail'),
    path('products/<int:pk>/update/', ProductView.as_view(action='update'), name='product-update'),
    path('products/<int:pk>/delete/', ProductView.as_view(action='delete'), name='product-delete'),
    path('products/print/', ProductView.as_view(action='print'), name='product-print'),
]

অথবা, আরও সংক্ষিপ্তভাবে:

urlpatterns = [
    path('products/', include([
        path('', ProductView.as_view(), name='product-list'),
        path('create/', ProductView.as_view(action='create'), name='product-create'),
        path('<int:pk>/', ProductView.as_view(action='detail'), name='product-detail'),
        path('<int:pk>/update/', ProductView.as_view(action='update'), name='product-update'),
        path('<int:pk>/delete/', ProductView.as_view(action='delete'), name='product-delete'),
        path('print/', ProductView.as_view(action='print'), name='product-print'),
    ])),
]

সারসংক্ষেপ

এই BaseCrudView ক্লাসটি একটি শক্তিশালী এবং পুনরায় ব্যবহারযোগ্য ভিউ ক্লাস যা আপনাকে বারবার একই CRUD কোড লেখা থেকে বাঁচাবে। এটি ব্যবহার করে আপনি:

  1. নতুন মডেলের জন্য দ্রুত CRUD অপারেশন তৈরি করতে পারবেন
  2. সার্চ, ফিল্টারিং, পেজিনেশন সহজেই যোগ করতে পারবেন
  3. প্রিন্ট ভিউ সহজেই যোগ করতে পারবেন
  4. কাস্টম ফিল্টার যোগ করতে পারবেন
  5. একই টেমপ্লেট ব্যবহার করে সব ফর্ম একই লুক পাবে

ভবিষ্যতে এই ক্লাসে আরও ফিচার যোগ করতে পারেন, যেমন:

  • এক্সপোর্ট ফাংশনালিটি (CSV, Excel, PDF)
  • বাল্ক অপারেশন (একাধিক আইটেম একসাথে ডিলিট/আপডেট)
  • অ্যাডভান্সড ফিল্টারিং
  • ইউজার পারমিশন চেক

Thought for 3 seconds### Django CRUD Implementation from Scratch

আমি আপনার জন্য একটি সম্পূর্ণ Django CRUD সিস্টেম তৈরি করব, যেখানে একটি সিম্পল প্রোডাক্ট মডেল ব্যবহার করা হবে। এতে মডেল, ফর্ম, ভিউ এবং URL সবকিছু থাকবে।

১. প্রজেক্ট সেটআপ

প্রথমে একটি Django প্রজেক্ট তৈরি করুন:

django-admin startproject crudproject
cd crudproject
python manage.py startapp products

settings.py ফাইলে আপনার অ্যাপ যোগ করুন:

INSTALLED_APPS = [
    # ...
    'products',
]

২. মডেল তৈরি করা

products/models.py ফাইলে একটি সিম্পল প্রোডাক্ট মডেল তৈরি করুন:

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "Categories"

class Product(models.Model):
    name = models.CharField(max_length=200)
    code = models.CharField(max_length=50, unique=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.PositiveIntegerField(default=0)
    description = models.TextField(blank=True, null=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
    image = models.ImageField(upload_to='products/', blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

মাইগ্রেশন তৈরি করুন এবং অ্যাপ্লাই করুন:

python manage.py makemigrations
python manage.py migrate

৩. ফর্ম তৈরি করা

products/forms.py ফাইলে প্রোডাক্ট ফর্ম তৈরি করুন:

from django import forms
from .models import Product, Category

class CategoryForm(forms.ModelForm):
    class Meta:
        model = Category
        fields = ['name', 'description']
        widgets = {
            'description': forms.Textarea(attrs={'rows': 3}),
        }

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ['name', 'code', 'price', 'stock', 'description', 'category', 'image']
        widgets = {
            'description': forms.Textarea(attrs={'rows': 3}),
            'price': forms.NumberInput(attrs={'step': '0.01'}),
        }

৪. বেস CRUD ভিউ তৈরি করা

products/views.py ফাইলে BaseCrudView ক্লাস তৈরি করুন:

from django.views import View
from django.shortcuts import render, redirect, get_object_or_404
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.contrib import messages
from django.db.models import Q

class BaseCrudView(View):
    model = None
    form_class = None
    template_name = 'common/form.html'
    list_template_name = None
    print_template_name = 'common/print.html'
    base_list_template = 'common/base-list.html'
    success_url = None
    list_url = None
    title = None
    subtitle = None
    paginate_by = 10
    search_fields = ['name']

    def dispatch(self, request, *args, **kwargs):
        self.action = kwargs.pop('action', 'list')
        return super().dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        if self.action == 'list':
            return self.list(request)
        elif self.action == 'create':
            return self.create_form(request)
        elif self.action == 'detail':
            return self.detail(request, kwargs.get('pk'))
        elif self.action == 'update':
            return self.update_form(request, kwargs.get('pk'))
        elif self.action == 'delete':
            return self.delete_confirm(request, kwargs.get('pk'))
        elif self.action == 'print':
            return self.print_view(request)
        return redirect(self.list_url)

    def post(self, request, *args, **kwargs):
        if self.action == 'create':
            return self.create_object(request)
        elif self.action == 'update':
            return self.update_object(request, kwargs.get('pk'))
        elif self.action == 'delete':
            return self.delete_object(request, kwargs.get('pk'))
        return redirect(self.list_url)

    def get_search_query(self, request):
        search_query = request.GET.get('search', '')
        if not search_query:
            return None
        q_objects = Q()
        for field in self.search_fields:
            q_objects |= Q(**{f"{field}__icontains": search_query})
        return q_objects

    def list(self, request):
        objects = self.model.objects.all()
        search_query = self.get_search_query(request)
        if search_query:
            objects = objects.filter(search_query)
        objects = self.apply_filters(request, objects)
        paginator = Paginator(objects, self.paginate_by)
        page = request.GET.get('page')
        try:
            objects = paginator.page(page)
        except PageNotAnInteger:
            objects = paginator.page(1)
        except EmptyPage:
            objects = paginator.page(paginator.num_pages)
        context = self.get_list_context(request, objects, paginator)
        return render(request, self.list_template_name, context)

    def apply_filters(self, request, queryset):
        return queryset

    def get_list_context(self, request, objects, paginator):
        return {
            'objects': objects,
            'model_name': self.model.__name__,
            'title': f"{self.title} List",
            'create_url': f"{self.list_url}create/",
            'list_url': self.list_url,
            'print_url': f"{self.list_url}print/",
            'is_paginated': objects.has_other_pages(),
            'paginator': paginator,
            'page_obj': objects,
            'request': request,
            'base_template': self.base_list_template,
        }

    def detail(self, request, pk):
        obj = get_object_or_404(self.model, pk=pk)
        form = self.form_class(instance=obj)
        context = {
            'form': form,
            'object': obj,
            'model_name': self.model.__name__,
            'title': f"View {self.title}",
            'subtitle': f"Details for {self.title.lower()}",
            'update_url': f"{self.list_url}{pk}/update/",
            'delete_url': f"{self.list_url}{pk}/delete/",
            'cancel_url': self.list_url,
            'is_detail_view': True,
            'is_disabled': True,
        }
        return render(request, self.template_name, context)

    def create_form(self, request):
        form = self.form_class()
        context = {
            'form': form,
            'title': f"Create {self.title}",
            'subtitle': f"Add a new {self.title.lower()}",
            'submit_text': 'Create',
            'cancel_url': self.list_url,
        }
        return render(request, self.template_name, context)

    def create_object(self, request):
        form = self.form_class(request.POST, request.FILES)
        if form.is_valid():
            obj = form.save()
            messages.success(request, f"{self.title} created successfully.")
            return redirect(self.success_url)
        context = {
            'form': form,
            'title': f"Create {self.title}",
            'subtitle': f"Add a new {self.title.lower()}",
            'submit_text': 'Create',
            'cancel_url': self.list_url,
        }
        return render(request, self.template_name, context)

    def update_form(self, request, pk):
        obj = get_object_or_404(self.model, pk=pk)
        form = self.form_class(instance=obj)
        context = {
            'form': form,
            'title': f"Update {self.title}",
            'subtitle': f"Modify {self.title.lower()} details",
            'submit_text': 'Update',
            'cancel_url': self.list_url,
        }
        return render(request, self.template_name, context)

    def update_object(self, request, pk):
        obj = get_object_or_404(self.model, pk=pk)
        form = self.form_class(request.POST, request.FILES, instance=obj)
        if form.is_valid():
            form.save()
            messages.success(request, f"{self.title} updated successfully.")
            return redirect(self.success_url)
        context = {
            'form': form,
            'title': f"Update {self.title}",
            'subtitle': f"Modify {self.title.lower()} details",
            'submit_text': 'Update',
            'cancel_url': self.list_url,
        }
        return render(request, self.template_name, context)

    def delete_confirm(self, request, pk):
        obj = get_object_or_404(self.model, pk=pk)
        context = {
            'object': obj,
            'title': f"Delete {self.title}",
            'subtitle': f"Are you sure you want to delete this {self.title.lower()}?",
            'cancel_url': self.list_url,
        }
        return render(request, 'common/delete_confirm.html', context)

    def delete_object(self, request, pk):
        obj = get_object_or_404(self.model, pk=pk)
        obj.delete()
        messages.success(request, f"{self.title} deleted successfully.")
        return redirect(self.list_url)

    def print_view(self, request):
        object_id = request.GET.get('id')
        if object_id:
            objects = self.model.objects.filter(pk=object_id)
        else:
            objects = self.model.objects.all()
        context = {
            'objects': objects,
            'model_name': self.model.__name__,
            'title': f"{self.title} Print View",
            'list_url': self.list_url,
        }
        return render(request, self.print_template_name, context)

৫. মডেল-স্পেসিফিক ভিউ তৈরি করা

এবার আমরা প্রোডাক্ট এবং ক্যাটাগরি মডেলের জন্য ভিউ তৈরি করব:

from django.urls import reverse_lazy
from .models import Product, Category
from .forms import ProductForm, CategoryForm

class ProductView(BaseCrudView):
    model = Product
    form_class = ProductForm
    list_template_name = 'products/product_list.html'
    success_url = reverse_lazy('product-list')
    list_url = reverse_lazy('product-list')
    title = 'Product'
    search_fields = ['name', 'code', 'category__name']

    def apply_filters(self, request, queryset):
        category_id = request.GET.get('category')
        if category_id:
            queryset = queryset.filter(category_id=category_id)
        return queryset

    def get_list_context(self, request, objects, paginator):
        context = super().get_list_context(request, objects, paginator)
        context['categories'] = Category.objects.all()
        context['selected_category'] = request.GET.get('category', '')
        return context

class CategoryView(BaseCrudView):
    model = Category
    form_class = CategoryForm
    list_template_name = 'products/category_list.html'
    success_url = reverse_lazy('category-list')
    list_url = reverse_lazy('category-list')
    title = 'Category'
    search_fields = ['name']

৬. URL কনফিগারেশন

products/urls.py ফাইল তৈরি করুন:

from django.urls import path
from .views import ProductView, CategoryView

urlpatterns = [
    # Product URLs
    path('products/', ProductView.as_view(), name='product-list'),
    path('products/create/', ProductView.as_view(action='create'), name='product-create'),
    path('products/<int:pk>/', ProductView.as_view(action='detail'), name='product-detail'),
    path('products/<int:pk>/update/', ProductView.as_view(action='update'), name='product-update'),
    path('products/<int:pk>/delete/', ProductView.as_view(action='delete'), name='product-delete'),
    path('products/print/', ProductView.as_view(action='print'), name='product-print'),

    # Category URLs
    path('categories/', CategoryView.as_view(), name='category-list'),
    path('categories/create/', CategoryView.as_view(action='create'), name='category-create'),
    path('categories/<int:pk>/', CategoryView.as_view(action='detail'), name='category-detail'),
    path('categories/<int:pk>/update/', CategoryView.as_view(action='update'), name='category-update'),
    path('categories/<int:pk>/delete/', CategoryView.as_view(action='delete'), name='category-delete'),
    path('categories/print/', CategoryView.as_view(action='print'), name='category-print'),
]

মেইন urls.py ফাইলে এই URL প্যাটার্নগুলো যোগ করুন:

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

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

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

৭. টেমপ্লেট তৈরি করা

প্রথমে টেমপ্লেট ডিরেক্টরি স্ট্রাকচার তৈরি করুন:

templates/
    base.html
    common/
        base-list.html
        form.html
        delete_confirm.html
        print.html
    products/
        product_list.html
        category_list.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 %}Product Management System{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            padding-top: 20px;
        }
        .navbar {
            margin-bottom: 20px;
        }
    </style>
    {% block extra_css %}{% endblock %}
</head>
<body>
    <div class="container">
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary rounded">
            <div class="container-fluid">
                <a class="navbar-brand" href="/">Product Management</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarNav">
                    <ul class="navbar-nav">
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'product-list' %}">Products</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'category-list' %}">Categories</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>

        {% if messages %}
            <div class="messages">
                {% for message in messages %}
                    <div class="alert alert-{{ message.tags }}">
                        {{ message }}
                    </div>
                {% endfor %}
            </div>
        {% endif %}

        <div class="content mt-4">
            {% block content %}{% endblock %}
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    {% block extra_js %}{% endblock %}
</body>
</html>

common/base-list.html

{% extends "base.html" %}

{% block content %}
<div class="card">
    <div class="card-header d-flex justify-content-between align-items-center">
        <h3>{{ title }}</h3>
        <a href="{{ create_url }}" class="btn btn-primary">Add New</a>
    </div>
    <div class="card-body">
        <div class="row mb-3">
            <div class="col-md-6">
                <form method="get" class="d-flex">
                    <input type="text" name="search" class="form-control me-2" placeholder="Search..." value="{{ request.GET.search|default:'' }}">
                    <button type="submit" class="btn btn-outline-primary">Search</button>
                </form>
            </div>
            <div class="col-md-6 text-end">
                <a href="{{ print_url }}" class="btn btn-outline-secondary" target="_blank">Print</a>
            </div>
        </div>

        {% block filters %}{% endblock %}

        {% block list_content %}{% endblock %}

        {% if is_paginated %}
        <nav aria-label="Page navigation">
            <ul class="pagination justify-content-center">
                {% if page_obj.has_previous %}
                <li class="page-item">
                    <a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.category %}&category={{ request.GET.category }}{% endif %}">« First</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.category %}&category={{ request.GET.category }}{% endif %}">Previous</a>
                </li>
                {% endif %}

                <li class="page-item disabled">
                    <span class="page-link">Page {{ page_obj.number }} of {{ paginator.num_pages }}</span>
                </li>

                {% if page_obj.has_next %}
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.category %}&category={{ request.GET.category }}{% endif %}">Next</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.category %}&category={{ request.GET.category }}{% endif %}">Last »</a>
                </li>
                {% endif %}
            </ul>
        </nav>
        {% endif %}
    </div>
</div>
{% endblock %}

common/form.html

{% extends "base.html" %}

{% block content %}
<div class="card">
    <div class="card-header">
        <h3>{{ title }}</h3>
        <p class="text-muted">{{ subtitle }}</p>
    </div>
    <div class="card-body">
        <form method="post" enctype="multipart/form-data">
            {% csrf_token %}

            {% for field in form %}
            <div class="mb-3">
                <label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
                {% if is_disabled %}
                    {% if field.field.widget.input_type == 'file' and object.image %}
                        <div>
                            <img src="{{ object.image.url }}" alt="{{ object.name }}" style="max-width: 200px; max-height: 200px;">
                        </div>
                    {% else %}
                        <div class="form-control bg-light">{{ field.value|default:"-" }}</div>
                    {% endif %}
                {% else %}
                    {{ field.errors }}
                    {{ field }}
                {% endif %}
                {% if field.help_text %}
                <div class="form-text">{{ field.help_text }}</div>
                {% endif %}
            </div>
            {% endfor %}

            <div class="mt-4">
                {% if not is_detail_view %}
                <button type="submit" class="btn btn-primary">{{ submit_text|default:"Submit" }}</button>
                {% endif %}

                {% if is_detail_view %}
                <a href="{{ update_url }}" class="btn btn-primary">Edit</a>
                <a href="{{ delete_url }}" class="btn btn-danger">Delete</a>
                {% endif %}

                <a href="{{ cancel_url }}" class="btn btn-secondary">Back</a>
            </div>
        </form>
    </div>
</div>
{% endblock %}

common/delete_confirm.html

{% extends "base.html" %}

{% block content %}
<div class="card">
    <div class="card-header bg-danger text-white">
        <h3>{{ title }}</h3>
    </div>
    <div class="card-body">
        <p>{{ subtitle }}</p>

        <div class="alert alert-warning">
            <p>You are about to delete: <strong>{{ object }}</strong></p>
            <p>This action cannot be undone.</p>
        </div>

        <form method="post">
            {% csrf_token %}
            <button type="submit" class="btn btn-danger">Confirm Delete</button>
            <a href="{{ cancel_url }}" class="btn btn-secondary">Cancel</a>
        </form>
    </div>
</div>
{% endblock %}

common/print.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 20px;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
        .header {
            text-align: center;
            margin-bottom: 20px;
        }
        .footer {
            margin-top: 30px;
            text-align: center;
            font-size: 12px;
        }
        @media print {
            .no-print {
                display: none;
            }
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>{{ title }}</h1>
        <p>Generated on: {% now "F j, Y H:i" %}</p>
    </div>

    <div class="no-print">
        <button onclick="window.print()">Print</button>
        <a href="{{ list_url }}"><button>Back to List</button></a>
    </div>

    {% if model_name == 'Product' %}
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Code</th>
                <th>Category</th>
                <th>Price</th>
                <th>Stock</th>
            </tr>
        </thead>
        <tbody>
            {% for obj in objects %}
            <tr>
                <td>{{ obj.id }}</td>
                <td>{{ obj.name }}</td>
                <td>{{ obj.code }}</td>
                <td>{{ obj.category.name }}</td>
                <td>{{ obj.price }}</td>
                <td>{{ obj.stock }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    {% elif model_name == 'Category' %}
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Description</th>
            </tr>
        </thead>
        <tbody>
            {% for obj in objects %}
            <tr>
                <td>{{ obj.id }}</td>
                <td>{{ obj.name }}</td>
                <td>{{ obj.description|default:"-" }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    {% endif %}

    <div class="footer">
        <p>© {% now "Y" %} Your Company. All rights reserved.</p>
    </div>
</body>
</html>

products/product_list.html

{% extends "common/base-list.html" %}

{% block filters %}
<div class="row mb-3">
    <div class="col-md-6">
        <form method="get" class="d-flex">
            <select name="category" class="form-select me-2">
                <option value="">All Categories</option>
                {% for category in categories %}
                <option value="{{ category.id }}" {% if selected_category == category.id|stringformat:"i" %}selected{% endif %}>{{ category.name }}</option>
                {% endfor %}
            </select>
            <button type="submit" class="btn btn-outline-primary">Filter</button>
        </form>
    </div>
</div>
{% endblock %}

{% block list_content %}
<div class="table-responsive">
    <table class="table table-striped table-hover">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Code</th>
                <th>Category</th>
                <th>Price</th>
                <th>Stock</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            {% for obj in objects %}
            <tr>
                <td>{{ obj.id }}</td>
                <td>{{ obj.name }}</td>
                <td>{{ obj.code }}</td>
                <td>{{ obj.category.name }}</td>
                <td>{{ obj.price }}</td>
                <td>{{ obj.stock }}</td>
                <td>
                    <div class="btn-group btn-group-sm">
                        <a href="{{ list_url }}{{ obj.id }}/" class="btn btn-info">View</a>
                        <a href="{{ list_url }}{{ obj.id }}/update/" class="btn btn-primary">Edit</a>
                        <a href="{{ list_url }}{{ obj.id }}/delete/" class="btn btn-danger">Delete</a>
                    </div>
                </td>
            </tr>
            {% empty %}
            <tr>
                <td colspan="7" class="text-center">No products found.</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
</div>
{% endblock %}

products/category_list.html

{% extends "common/base-list.html" %}

{% block list_content %}
<div class="table-responsive">
    <table class="table table-striped table-hover">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Description</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            {% for obj in objects %}
            <tr>
                <td>{{ obj.id }}</td>
                <td>{{ obj.name }}</td>
                <td>{{ obj.description|truncatechars:50|default:"-" }}</td>
                <td>
                    <div class="btn-group btn-group-sm">
                        <a href="{{ list_url }}{{ obj.id }}/" class="btn btn-info">View</a>
                        <a href="{{ list_url }}{{ obj.id }}/update/" class="btn btn-primary">Edit</a>
                        <a href="{{ list_url }}{{ obj.id }}/delete/" class="btn btn-danger">Delete</a>
                    </div>
                </td>
            </tr>
            {% empty %}
            <tr>
                <td colspan="4" class="text-center">No categories found.</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
</div>
{% endblock %}

৮. সেটিংস আপডেট

settings.py ফাইলে টেমপ্লেট এবং স্ট্যাটিক/মিডিয়া ফাইল কনফিগারেশন যোগ করুন:

import os

# ...

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# ...

# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

# Media files (Uploads)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

৯. প্রজেক্ট রান করা

প্রজেক্ট রান করার জন্য নিচের কমান্ড ব্যবহার করুন:

python manage.py runserver

এবার আপনি http://127.0.0.1:8000/products/ এবং http://127.0.0.1:8000/categories/ URL-এ গিয়ে আপনার CRUD অপারেশন ব্যবহার করতে পারবেন।

১০. ব্যবহার পদ্ধতি

নতুন মডেলের জন্য CRUD তৈরি করা

যদি আপনি নতুন কোন মডেলের জন্য CRUD তৈরি করতে চান, তাহলে নিচের ধাপগুলো অনুসরণ করুন:

  1. নতুন মডেল তৈরি করুন
  2. মডেলের জন্য ফর্ম তৈরি করুন
  3. BaseCrudView এক্সটেন্ড করে নতুন ভিউ তৈরি করুন
  4. URL কনফিগারেশন যোগ করুন
  5. প্রয়োজনীয় টেমপ্লেট তৈরি করুন

উদাহরণস্বরূপ, একটি Supplier মডেলের জন্য:

# models.py
class Supplier(models.Model):
    name = models.CharField(max_length=200)
    contact_person = models.CharField(max_length=100)
    email = models.EmailField()
    phone = models.CharField(max_length=20)
    address = models.TextField()

    def __str__(self):
        return self.name

# forms.py
class SupplierForm(forms.ModelForm):
    class Meta:
        model = Supplier
        fields = ['name', 'contact_person', 'email', 'phone', 'address']
        widgets = {
            'address': forms.Textarea(attrs={'rows': 3}),
        }

# views.py
class SupplierView(BaseCrudView):
    model = Supplier
    form_class = SupplierForm
    list_template_name = 'suppliers/supplier_list.html'
    success_url = reverse_lazy('supplier-list')
    list_url = reverse_lazy('supplier-list')
    title = 'Supplier'
    search_fields = ['name', 'contact_person', 'email']

# urls.py
urlpatterns += [
    path('suppliers/', SupplierView.as_view(), name='supplier-list'),
    path('suppliers/create/', SupplierView.as_view(action='create'), name='supplier-create'),
    path('suppliers/<int:pk>/', SupplierView.as_view(action='detail'), name='supplier-detail'),
    path('suppliers/<int:pk>/update/', SupplierView.as_view(action='update'), name='supplier-update'),
    path('suppliers/<int:pk>/delete/', SupplierView.as_view(action='delete'), name='supplier-delete'),
    path('suppliers/print/', SupplierView.as_view(action='print'), name='supplier-print'),
]

১১. উন্নত ফিচার যোগ করা

১১.১. অ্যাডভান্সড ফিল্টারিং

আরও উন্নত ফিল্টারিং যোগ করতে apply_filters মেথড ওভাররাইড করুন:

def apply_filters(self, request, queryset):
    # Price range filter
    min_price = request.GET.get('min_price')
    max_price = request.GET.get('max_price')

    if min_price:
        queryset = queryset.filter(price__gte=min_price)
    if max_price:
        queryset = queryset.filter(price__lte=max_price)

    # Date range filter
    start_date = request.GET.get('start_date')
    end_date = request.GET.get('end_date')

    if start_date:
        queryset = queryset.filter(created_at__gte=start_date)
    if end_date:
        queryset = queryset.filter(created_at__lte=end_date)

    return queryset

১১.২. এক্সপোর্ট ফাংশনালিটি

CSV এক্সপোর্ট ফাংশনালিটি যোগ করুন:

import csv
from django.http import HttpResponse

def export_csv(self, request):
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = f'attachment; filename="{self.model.__name__.lower()}_export.csv"'

    writer = csv.writer(response)

    # Write header row
    fields = [field.name for field in self.model._meta.fields]
    writer.writerow(fields)

    # Write data rows
    objects = self.model.objects.all()
    for obj in objects:
        row = [getattr(obj, field) for field in fields]
        writer.writerow(row)

    return response

১১.৩. বাল্ক অপারেশন

বাল্ক ডিলিট অপারেশন যোগ করুন:

def bulk_delete(self, request):
    if request.method == 'POST':
        ids = request.POST.getlist('selected_items')
        if ids:
            self.model.objects.filter(id__in=ids).delete()
            messages.success(request, f"{len(ids)} {self.title}(s) deleted successfully.")
        else:
            messages.warning(request, "No items selected for deletion.")
    return redirect(self.list_url)

১২. সারসংক্ষেপ

এই প্রজেক্টে আমরা একটি সম্পূর্ণ Django CRUD সিস্টেম তৈরি করেছি যা:

  1. একটি বেস ক্লাস ব্যবহার করে যেকোনো মডেলের জন্য CRUD অপারেশন সহজে ইমপ্লিমেন্ট করতে পারে
  2. সার্চ, ফিল্টারিং, পেজিনেশন সাপোর্ট করে
  3. প্রিন্ট ভিউ সাপোর্ট করে
  4. ফাইল আপলোড সাপোর্ট করে
  5. সহজে নতুন মডেলের জন্য এক্সটেন্ড করা যায়

এই সিস্টেম ব্যবহার করে আপনি দ্রুত এবং কার্যকরভাবে Django অ্যাপ্লিকেশন তৈরি করতে পারবেন, যেখানে CRUD অপারেশন প্রয়োজন।

How can we help?