# 📋 Tổng Quan Dự Án BMC Website

**Ngày tạo**: 18/04/2026  
**Dự án**: Website Công ty BMC Group  
**Domain**: bmcgroup.com.vn  
**Framework**: Django 4.0  
**Database**: PostgreSQL  

---

## 1. Giới Thiệu Dự Án

### 1.1 Mục Đích
Website thương mại điện tử của công ty **BMC Group** - nhà sản xuất thuốc bảo vệ thực vật và thiết bị nông nghiệp. Website cung cấp:
- Catalog sản phẩm (thuốc sâu, thuốc trừ bệnh, thuốc cỏ, thuốc sinh trưởng, bình bơm)
- Hệ thống blog/kiến thức
- Thông báo và tin tức
- Tuyển dụng
- Biểu mẫu liên hệ

### 1.2 Đặc Điểm Nổi Bật
- ✅ **Đa ngôn ngữ**: Hỗ trợ Tiếng Việt & Tiếng Anh
- ✅ **RichText Editor**: Sử dụng CKEditor cho nội dung động
- ✅ **Full-text Search**: PostgreSQL FTS cho blog
- ✅ **File Upload Safe**: Xử lý an toàn cho upload files
- ✅ **Admin Panel**: Django admin tùy chỉnh

---

## 2. Kiến Trúc Dự Án

### 2.1 Cấu Trúc Thư Mục
```
website-bmc/
├── BMC/                          # Django Project Config
│   ├── __init__.py
│   ├── asgi.py                  # Async Gateway
│   ├── settings.py              # Cấu hình chính
│   ├── urls.py                  # URL routing tổng
│   └── wsgi.py                  # WSGI config
│
├── BmcBase/                      # Django App Chính
│   ├── models.py                # 10 Data Models
│   ├── views.py                 # 15+ View Functions
│   ├── urls.py                  # URL routing
│   ├── forms.py                 # Django Forms
│   ├── admin.py                 # Admin customization
│   ├── apps.py
│   ├── tests.py
│   ├── migrations/              # Database migrations
│   ├── templates/BmcBase/       # HTML templates (20+ files)
│   └── static/                  # CSS, JS, CKEditor, Images
│       ├── css/
│       ├── js/
│       ├── img/
│       ├── lib/
│       └── ckeditor/
│
├── media/                        # Uploaded Files
│   ├── banners/                 # Article banners
│   ├── products/                # Product images
│   ├── uploads/                 # CKEditor uploads
│   └── contacts/                # Contact form attachments
│
├── staticfiles/                 # Collected static files
├── manage.py                    # Django CLI
└── db.sqlite3 (hoặc PostgreSQL)
```

### 2.2 Technology Stack
| Layer | Technology |
|-------|-----------|
| **Backend** | Django 4.0 (Python) |
| **Database** | PostgreSQL 12+ |
| **Frontend** | HTML5, CSS3, Bootstrap 5, JavaScript |
| **Rich Editor** | CKEditor 4 |
| **Images** | Pillow |
| **Search** | PostgreSQL Full-Text Search |
| **i18n** | Django i18n (Vietnamese, English) |
| **Web Server** | Gunicorn + Nginx (production) |

---

## 3. Database Schema

### 3.1 Core Models (10 Models)

#### **TheLoaiBaiViet** (Blog Category)
```python
- id: BigAutoField (PK)
- ten: CharField(255)                 # Vietnamese name
- ten_tieng_anh: CharField(255)      # English name
- mo_ta: TextField                    # Description
- mo_ta_tieng_anh: RichTextField     # English description
```

#### **KienThuc** (Blog Post)
```python
- id: BigAutoField (PK)
- ten: CharField(255)
- ten_tieng_anh: CharField(255)
- ngay_dang: DateField               # Publish date
- the_loai: ManyToManyField (TheLoaiBaiViet)
- mo_ta: RichTextField               # Summary
- mo_ta_tieng_anh: RichTextField
- noi_dung: RichTextUploadingField   # Full content (with images)
- noi_dung_tieng_anh: RichTextUploadingField
- banner: ImageField(upload_to='banners/')
- ordering: [-ngay_dang]             # Newest first
```

#### **BinhLuan** (Comments)
```python
- id: BigAutoField (PK)
- ten_nguoi_dang: CharField(255)     # Commenter name
- thong_tin_lien_he: CharField(255)  # Email/Phone
- tieu_de: CharField(255)            # Comment title
- noi_dung: TextField                # Comment content
- ngay_dang: DateField               # Comment date
- trang_thai_da_duyet: BooleanField(default=False)  # Approval status
- bai_viet: ForeignKey(KienThuc)     # Related post
- Related name: 'cac_binh_luan'
```

#### **ThongBao** (Announcements)
```python
- id: BigAutoField (PK)
- ten: CharField(255)
- ten_tieng_anh: CharField(255)
- ngay_dang: DateField
- banner: ImageField
- noi_dung: RichTextUploadingField
- noi_dung_tieng_anh: RichTextUploadingField
- ordering: [-ngay_dang]
```

#### **TinTuc** (News)
```python
- id: BigAutoField (PK)
- ten: CharField(255)
- ten_tieng_anh: CharField(255)
- ngay_dang: DateField
- banner: ImageField
- noi_dung: RichTextUploadingField
- noi_dung_tieng_anh: RichTextUploadingField
- ordering: [-ngay_dang]
```

#### **SanPham** (Main Products)
```python
- id: BigAutoField (PK)
- ten: CharField(255)
- ten_en: CharField(255)
- mo_ta_ngan: CharField(255)         # Short description
- mo_ta_ngan_en: CharField(255)
- hinh_anh: ImageField(upload_to='products/')
- loai: CharField(100)               # Type (thuốc sâu, thuốc trừ bệnh, etc.)
- loai_en: CharField(100)
- thanh_phan: RichTextUploadingField # Components
- thanh_phan_en: RichTextUploadingField
- hang_sx: TextField                 # Manufacturer
- hang_sx_en: TextField
- dac_tinh: RichTextUploadingField   # Characteristics
- dac_tinh_en: RichTextUploadingField
- hdsd: RichTextUploadingField       # Usage instructions
- hdsd_en: RichTextUploadingField
- khuyen_mai: DecimalField(5,2)      # Discount %
- gia: DecimalField(10,2)            # Price
```

#### **SanPhamBinhBom** (Pump Products)
```python
- id: BigAutoField (PK)
- ten: CharField(255)
- hinh_anh: ImageField
- ac_quy: CharField(255)             # Battery
- dung_tich: CharField(255)          # Capacity
- trong_luong: CharField(255)        # Weight
- thong_so_khac: TextField           # Other specs
- gia: DecimalField(10,2)
```

#### **Careers** (Job Listings)
```python
- id: BigAutoField (PK)
- tieu_de: CharField(100)            # Job title
- tieu_de_en: CharField(100)
- ngay_dang: DateField
- noi_dung_cong_viec: RichTextUploadingField  # Job description
- noi_dung_cong_viec_en: RichTextUploadingField
- muc_luong_min: CharField(20)       # Min salary
- muc_luong_max: CharField(20)       # Max salary
- property: muc_luong_range          # Calculated range
```

#### **FormLienHe** (Contact Form)
```python
- id: BigAutoField (PK)
- ten: CharField(255)
- email: EmailField
- so_dien_thoai: CharField(20)
- loai_khach_hang: CharField(20)     # Choice: new/old customer
- noi_dung: TextField
- file: FileField(upload_to='contacts/')
- original_file_name: CharField(255) # Store original filename
```

#### **FormTuVanSanPham** (Product Consultation)
```python
- id: BigAutoField (PK)
- sdt: CharField(255)                # Phone number
- tenSp: CharField(255)              # Product name
```

### 3.2 Relations
```
TheLoaiBaiViet
    ↑ (M2M)
KienThuc
    ↑ (1:N via related_name='cac_binh_luan')
BinhLuan
```

---

## 4. URL Routing

### 4.1 Main Routes (BMC/urls.py)
```python
urlpatterns = i18n_patterns(
    path("", include("BmcBase.urls")),
    path('admin/', admin.site.urls),
    path('ckeditor/', include('ckeditor_uploader.urls')),
)
```

### 4.2 App Routes (BmcBase/urls.py)

| URL | View | Purpose |
|-----|------|---------|
| `/` | `index()` | Homepage - showcase products & news |
| `/about/` | `about()` | About page |
| `/gioi-thieu/` | `about()` | Vietnamese version |
| `/products/` | `products()` | Product catalog (5 categories) |
| `/product/` | `products()` | Alternative route |
| `/products/<id>/` | `product_detail()` | Product details |
| `/products/binh-bom/<id>/` | `product_binhbom()` | Pump product details |
| `/blog/` | `blog()` | Blog list |
| `/blog/the-loai/<id>/` | `blog()` | Blog by category |
| `/blog/bai-viet/<id>/` | `blog_post()` | Blog post detail + comments |
| `/blog/tim-kiem/` | `blog_search()` | Full-text search |
| `/careers/` | `careers()` | Job listings |
| `/career/<id>/` | `career()` | Job detail |
| `/tuyen-dung/` | `careers()` | Vietnamese version |
| `/contact/` | `contact()` | Contact form page |
| `/lien-he/` | `contact()` | Vietnamese version |
| `/form/` | `form_submit()` | Contact form handler |
| `/tu-van/` | `form_submit_product()` | Product consultation handler |
| `/noti/` | `noti()` | Announcements list |
| `/noti/<id>/` | `noti_post()` | Announcement detail |
| `/news/<id>/` | `news_detail()` | News detail |
| `/imei_check_online/` | `display_image()` | Utility page |
| `/i18n/` | `set_language()` | Language switcher |
| `/admin/` | Django Admin | Admin panel |

**Note**: Routes wrapped in `i18n_patterns()` → URLs prefixed with language code: `/vi/...` or `/en/...`

---

## 5. View Logic Details

### 5.1 Homepage (`index()`)

**Flow**:
```
GET / → index()
├─ Check for 'q' parameter (search query)
├─ Load all products grouped by type:
│  ├─ products[9]                    # Latest 9
│  ├─ thuocsaus[9]                   # Insecticides (thuốc sâu)
│  ├─ thuoctrubenhs[9]               # Disease control
│  ├─ thuoccos[9]                    # Herbicides
│  ├─ thuocsinhtruongs[9]            # Growth stimulants
│  └─ binhbom[9]                     # Pumps
├─ Load recent news
├─ IF query exists:
│  └─ Redirect to product_detail if found
└─ Render homepage with context
```

**Template**: `index.html`  
**Data Passed**:
```python
{
    'products': SanPham[9],
    'thuocsaus': SanPham[9],
    'thuoctrubenhs': SanPham[9],
    'thuoccos': SanPham[9],
    'thuocsinhtruongs': SanPham[9],
    'binh_bom': SanPhamBinhBom[9],
    'news': TinTuc[]
}
```

### 5.2 Products Page (`products()`)

**Flow**:
```
GET /products/ → products()
├─ Initialize 6 paginators (one per category + all)
├─ For each category:
│  ├─ Fetch objects
│  ├─ Paginate by 12/page
│  ├─ Get page number from query param
│  └─ Handle PageNotAnInteger & EmptyPage
└─ Render all 6 paginators simultaneously
```

**Issues**: 
- ⚠️ 6 paginators on one page → Confusing
- ⚠️ Product type hardcoded as string

**Pagination Params**:
- `?products_page=2` - All products
- `?thuocsaus_page=2` - Insecticides
- `?thuoctrubenhs_page=2` - Disease control
- `?thuoccos_page=2` - Herbicides
- `?thuocsinhtruongs_page=2` - Growth stimulants
- `?binh_bom_page=2` - Pumps

### 5.3 Blog System

#### **Blog List (`blog(id_the_loai=None)`)**
```
GET /blog/ OR /blog/the-loai/<id>/
├─ IF category specified:
│  └─ Filter KienThuc.objects.filter(the_loai=category)
├─ Sort by -ngay_dang (newest first)
├─ Paginate 10/page
└─ Render with categories dropdown
```

#### **Blog Search (`blog_search()`)**
```
GET /blog/tim-kiem/?q=keyword
├─ Build PostgreSQL SearchVector (6 fields):
│  ├─ ten (title VN)
│  ├─ ten_tieng_anh (title EN)
│  ├─ mo_ta (summary VN)
│  ├─ mo_ta_tieng_anh (summary EN)
│  ├─ noi_dung (content VN)
│  └─ noi_dung_tieng_anh (content EN)
├─ SearchQuery & SearchRank (FTS)
├─ Filter & order by rank
├─ Paginate 10/page
└─ Render results with relevance
```

**Advantages**: Full-text search, multi-language support

#### **Blog Post Detail (`blog_post(id_bai_viet)`)**
```
GET /blog/bai-viet/<id>/
├─ Get KienThuc by ID
├─ Fetch approved comments (limit 10)
├─ Find related posts:
│  └─ By category (max 5)
├─ IF POST (comment submission):
│  ├─ Validate BinhLuanForm
│  ├─ Create comment (trang_thai_da_duyet=False)
│  └─ Save to DB
├─ ELSE:
│  └─ Create empty form
└─ Render post + comments + form
```

**Comment Workflow**:
1. User submits comment
2. `trang_thai_da_duyet=False` (awaiting approval)
3. Admin must approve via Django admin
4. Only approved comments show

### 5.4 Contact Forms

#### **Contact Form (`form_submit()`)**
```
POST /form/
├─ Extract form data:
│  ├─ name
│  ├─ email
│  ├─ phone
│  ├─ user_type (khach_hang_moi or khach_hang_cu)
│  ├─ message
│  └─ file (optional)
├─ IF file:
│  ├─ Call _extract_original_upload_name(file)
│  │  └─ Store original filename
│  ├─ Call _build_safe_upload_name(file)
│  │  └─ Return: slugify(filename) + uuid[:10] + ext
│  └─ Save file with safe name
├─ Create FormLienHe record
└─ Redirect to /
```

**Safe Upload Logic**:
```python
def _build_safe_upload_name(uploaded_file):
    original_name = uploaded_file.name or "uploaded-file"
    base_name, extension = os.path.splitext(original_name)
    safe_base_name = slugify(base_name)
    if not safe_base_name:
        safe_base_name = "uploaded-file"
    unique_suffix = uuid.uuid4().hex[:10]
    return f"{safe_base_name}-{unique_suffix}{extension.lower()}"
```

**Example**:
- Original: `Báo cáo Q1.pdf`
- Safe: `bao-cao-q1-a1b2c3d4e5.pdf`

#### **Product Consultation (`form_submit_product()`)**
```
POST /tu-van/
├─ Extract:
│  ├─ phone_number → sdt
│  └─ product_name → tenSp
├─ Create FormTuVanSanPham
└─ Redirect to /
```

---

## 6. Configuration

### 6.1 Settings (BMC/settings.py)

#### **Database**
```python
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'bmc',
        'USER': 'admin',
        'PASSWORD': '123@456a',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}
```

#### **Installed Apps**
```python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'BmcBase',
    'ckeditor',
    'ckeditor_uploader',
]
```

#### **i18n Settings**
```python
LANGUAGE_CODE = 'vi'
LANGUAGES = [
    ("en", _("English")),
    ("vi", _("Vietnamese")),
]
LOCALE_PATHS = [BASE_DIR / 'locale/']
```

#### **CKEditor Config**
- Rich toolbar with many features
- Image upload support
- YouTube embed
- Code snippet plugin
- Height: 400px
- Filebrowser: 725x940px

#### **Security Settings**
```python
DEBUG = True                    # ⚠️ DANGER in production!
ALLOWED_HOSTS = [
    "bmcgroup.com.vn",
    "www.bmcgroup.com.vn"
]
```

#### **Static & Media Files**
```python
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
```

---

## 7. Django Admin Customization

### 7.1 Registered Models
```python
# Basic registration (default list view)
admin.site.register(TinTuc)
admin.site.register(SanPham)
admin.site.register(SanPhamBinhBom)
admin.site.register(FormLienHe)

# Customized with list_display
admin.site.register(Careers, CareersAdmin)          # Show: id, tieu_de, ngay_dang
admin.site.register(TheLoaiBaiViet, TheLoaiBaiVietAdmin)
admin.site.register(ThongBao, ThongBaoAdmin)        # Show: id, ten, ngay_dang
admin.site.register(KienThuc, KienThucAdmin)        # Show: id, ten, ngay_dang, mo_ta
admin.site.register(BinhLuan, BinhLuanAdmin)        # list_editable: trang_thai_da_duyet
admin.site.register(FormTuVanSanPham, FormTuVanSanPhamAdmin)
```

### 7.2 Comment Approval Flow
```
User submits comment on blog post
    ↓
BinhLuan created with trang_thai_da_duyet=False
    ↓
Admin checks: /admin/BmcBase/binhluan/
    ↓
Admin ticks checkbox: trang_thai_da_duyet ✓
    ↓
Comment visible on blog post (filtered by trang_thai_da_duyet=True)
```

---

## 8. Templates Structure

### 8.1 Template Inheritance
```
base.html (master template)
├─ header (navigation, language switcher)
├─ {% block content %}
└─ footer
    │
    ├── index.html (homepage)
    ├── about.html
    ├── products.html (6 tabs with paginators)
    ├── product.html (detail)
    ├── products-binhbom.html
    ├── blog.html (list with categories)
    ├── blog_post.html (detail + comments)
    ├── blog_search_results.html
    ├── careers.html
    ├── career.html
    ├── contact.html
    ├── noti.html (announcements)
    ├── noti_post.html
    ├── news.html (news list)
    ├── news_detail.html
    └── 404.html
```

### 8.2 Key Features
- **i18n Tags**: `{% load i18n %}`, `{% trans %}`, `{% get_current_language %}`
- **Static Files**: `{% load static %}`, `{% static 'css/style.css' %}`
- **RichText**: Display CKEditor HTML with `{{ object.noi_dung|safe }}`
- **Pagination**: Bootstrap pagination with page numbers
- **Language Switcher**: Form POST to `/i18n/` with next page

---

## 9. Forms

### 9.1 BinhLuanForm
```python
class BinhLuanForm(ModelForm):
    class Meta:
        model = BinhLuan
        fields = ["ten_nguoi_dang", "thong_tin_lien_he", "tieu_de", "noi_dung"]
        
        widgets = {
            "ten_nguoi_dang": TextInput(class="form-control form-control-lg"),
            "thong_tin_lien_he": TextInput(class="form-control form-control-lg"),
            "tieu_de": TextInput(class="form-control form-control-lg"),
            "noi_dung": Textarea(class="form-control form-control-lg", rows=7),
        }
```

---

## 10. Current Issues & Improvements

### 🔴 Critical Issues
| Issue | Impact | Fix |
|-------|--------|-----|
| `DEBUG = True` | Security risk in production | Set to False, use settings.local |
| Hardcoded credentials | Database exposure | Use environment variables |
| Secret key exposed | Session/CSRF bypass | Move to .env file |

### 🟡 Medium Issues
| Issue | Impact | Fix |
|-------|--------|-----|
| Multiple paginators (products page) | Confusing UX | Split into tabs/separate pages |
| Product type hardcoded | Not scalable | Create ProductType model |
| Simple search logic | Poor UX | Already good with FTS blog search |
| Manual comment approval | Bad workflow | Add email notifications |
| No caching | Slow on traffic | Add Django cache framework |

### 🟠 Low Priority
- Add slug fields for better URLs
- Optimize queries (select_related, prefetch_related)
- Add logging
- Add tests
- Add API endpoints (DRF)

---

## 11. Deployment Notes

### 11.1 Development
```bash
python manage.py runserver
```

### 11.2 Production Setup
```bash
# Collect static files
python manage.py collectstatic

# Run migrations
python manage.py migrate

# Gunicorn + Nginx
gunicorn BMC.wsgi:application --bind 0.0.0.0:8000
```

### 11.2 Environment Variables (Recommended)
```bash
# .env file
DEBUG=False
SECRET_KEY=your-secret-key-here
DB_NAME=bmc
DB_USER=admin
DB_PASSWORD=your-db-password
DB_HOST=localhost
DB_PORT=5432
ALLOWED_HOSTS=bmcgroup.com.vn,www.bmcgroup.com.vn
```

---

## 12. File Upload Locations

| Type | Upload Path | Extension |
|------|-------------|-----------|
| Product images | `/media/products/` | .jpg, .png, .gif |
| Article banners | `/media/banners/` | .jpg, .png, .gif |
| CKEditor uploads | `/media/uploads/` | All types |
| Contact attachments | `/media/contacts/` | All types |

---

## 13. Development Roadmap

### Phase 1: Bug Fixes
- [ ] Fix DEBUG settings
- [ ] Move credentials to .env
- [ ] Fix product page UI (too many paginators)

### Phase 2: Feature Enhancements
- [ ] Email notifications for contact forms
- [ ] Auto-approve/email comments
- [ ] Product type model (replace string)
- [ ] Add product filters/search
- [ ] Add product reviews/ratings

### Phase 3: Performance
- [ ] Database query optimization
- [ ] Caching strategy
- [ ] Image optimization/CDN
- [ ] API endpoints (REST)

### Phase 4: Advanced Features
- [ ] Shopping cart (if e-commerce)
- [ ] Payment integration
- [ ] Newsletter/email marketing
- [ ] Analytics/tracking

---

## 14. Contact & Support

**Project Owner**: Huy Nguyen  
**Email**: khabmcit@gmail.com  
**Repository**: d:\Huy\Project\programing\website-bmc  

---

**Document Version**: 1.0  
**Last Updated**: 18/04/2026
