跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python

Python Web 框架 Django 核心功能与实战

Django 是 Python 开源 Web 框架,遵循 MVC 架构。文章涵盖安装、项目创建、模型定义、视图函数、表单处理、模板渲染、路由配置及用户认证。通过产品管理系统实战案例,演示了分类管理、搜索功能及权限控制等核心功能的实现流程,帮助开发者掌握 Django 全栈开发技能。

CryptoLab发布于 2026/3/27更新于 2026/7/2333 浏览
Python Web 框架 Django 核心功能与实战

Django Web 框架核心功能与实战

学习目标与重点

学习目标:掌握 Django 框架的核心功能,包括模型、视图、模板、表单、认证、权限管理等;理解 Django 的 MVC 架构;通过实战案例开发一个完整的 Web 应用。

学习重点:Django 项目结构、模型定义、视图函数、模板渲染、表单处理、用户认证、权限管理、数据库操作。

Django 框架概述

什么是 Django

Django 是一个开源的 Python Web 框架,遵循 MVC(Model-View-Controller)架构,提供了完整的 Web 开发解决方案,包括数据库操作、表单处理、用户认证、权限管理等。

Django 的特点
  • 快速开发:提供了丰富的内置功能,如 ORM、模板引擎、表单处理等,减少了开发时间。
  • 安全性:内置了 CSRF 保护、XSS 防护、SQL 注入防护等安全措施。
  • 可扩展性:支持插件化开发,有大量的第三方库和应用。
  • 文档完善:提供了详细的官方文档和示例代码。
Django 的安装
pip install django

创建 Django 项目

创建项目
django-admin startproject myproject
创建应用
cd myproject
python manage.py startapp myapp
项目结构
myproject/
├── manage.py
├── myapp/
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations/
│   ├── models.py
│   ├── tests.py
│   └── views.py
└── myproject/
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

模型定义

定义模型
# myapp/models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    description = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def ():
         .name
__str__
self
return
self
迁移数据库
python manage.py makemigrations
python manage.py migrate

视图函数

定义视图函数
# myapp/views.py
from django.shortcuts import render, get_object_or_404, redirect
from myapp.models import Product
from myapp.forms import ProductForm

def product_list(request):
    products = Product.objects.all()
    return render(request, 'myapp/product_list.html', {'products': products})

def product_detail(request, product_id):
    product = get_object_or_404(Product, pk=product_id)
    return render(request, 'myapp/product_detail.html', {'product': product})

def product_create(request):
    if request.method == 'POST':
        form = ProductForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('product_list')
    else:
        form = ProductForm()
    return render(request, 'myapp/product_form.html', {'form': form})

def product_update(request, product_id):
    product = get_object_or_404(Product, pk=product_id)
    if request.method == 'POST':
        form = ProductForm(request.POST, instance=product)
        if form.is_valid():
            form.save()
            return redirect('product_list')
    else:
        form = ProductForm(instance=product)
    return render(request, 'myapp/product_form.html', {'form': form})

def product_delete(request, product_id):
    product = get_object_or_404(Product, pk=product_id)
    if request.method == 'POST':
        product.delete()
        return redirect('product_list')
    return render(request, 'myapp/product_delete.html', {'product': product})

表单处理

定义表单
# myapp/forms.py
from django import forms
from myapp.models import Product

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ['name', 'price', 'description']

模板渲染

定义模板
<!-- myapp/templates/myapp/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}产品管理系统{% endblock %}</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="{% url 'product_list' %}">产品管理系统</a>
        <div class="collapse navbar-collapse">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a class="nav-link" href="{% url 'product_list' %}">产品列表</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{% url 'product_create' %}">添加产品</a>
                </li>
            </ul>
        </div>
    </nav>
    <div class="container mt-4"> {% block content %}{% endblock %} </div>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<!-- myapp/templates/myapp/product_list.html -->
{% extends 'myapp/base.html' %}
{% block title %}产品列表{% endblock %}
{% block content %}
<h1>产品列表</h1>
<table class="table table-striped">
    <thead>
        <tr>
            <th>名称</th>
            <th>价格</th>
            <th>描述</th>
            <th>创建时间</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        {% for product in products %}
        <tr>
            <td>{{ product.name }}</td>
            <td>{{ product.price }}</td>
            <td>{{ product.description }}</td>
            <td>{{ product.created_at }}</td>
            <td>
                <a href="{% url 'product_detail' product.id %}" class="btn btn-primary btn-sm">查看</a>
                <a href="{% url 'product_update' product.id %}" class="btn btn-secondary btn-sm">编辑</a>
                <a href="{% url 'product_delete' product.id %}" class="btn btn-danger btn-sm">删除</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>
{% endblock %}
<!-- myapp/templates/myapp/product_detail.html -->
{% extends 'myapp/base.html' %}
{% block title %}产品详情{% endblock %}
{% block content %}
<h1>产品详情</h1>
<div class="card">
    <div class="card-body">
        <h5 class="card-title">{{ product.name }}</h5>
        <p class="card-text">{{ product.description }}</p>
        <p class="card-text">价格:{{ product.price }}</p>
        <p class="card-text">创建时间:{{ product.created_at }}</p>
        <p class="card-text">更新时间:{{ product.updated_at }}</p>
        <a href="{% url 'product_update' product.id %}" class="btn btn-secondary">编辑</a>
        <a href="{% url 'product_delete' product.id %}" class="btn btn-danger">删除</a>
    </div>
</div>
{% endblock %}
<!-- myapp/templates/myapp/product_form.html -->
{% extends 'myapp/base.html' %}
{% block title %}添加/编辑产品{% endblock %}
{% block content %}
<h1>{% if form.instance.pk %}编辑产品{% else %}添加产品{% endif %}</h1>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">保存</button>
    <a href="{% url 'product_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}
<!-- myapp/templates/myapp/product_delete.html -->
{% extends 'myapp/base.html' %}
{% block title %}删除产品{% endblock %}
{% block content %}
<h1>删除产品</h1>
<p>你确定要删除产品 "{{ product.name }}" 吗?</p>
<form method="post">
    {% csrf_token %}
    <button type="submit" class="btn btn-danger">确认删除</button>
    <a href="{% url 'product_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}

路由配置

应用路由
# myapp/urls.py
from django.urls import path
from myapp import views

urlpatterns = [
    path('', views.product_list, name='product_list'),
    path('product/<int:product_id>/', views.product_detail, name='product_detail'),
    path('product/create/', views.product_create, name='product_create'),
    path('product/<int:product_id>/update/', views.product_update, name='product_update'),
    path('product/<int:product_id>/delete/', views.product_delete, name='product_delete'),
]
项目路由
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include

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

用户认证与权限管理

配置认证系统
# myproject/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
创建用户
python manage.py createsuperuser
权限管理
# myapp/admin.py
from django.contrib import admin
from myapp.models import Product

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['name', 'price', 'description', 'created_at', 'updated_at']
    list_filter = ['created_at']
    search_fields = ['name', 'description']

静态文件管理

配置静态文件
# myproject/settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]
加载静态文件
<!-- myapp/templates/myapp/base.html -->
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}产品管理系统{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
    ... 
    <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
</body>
</html>

实战案例:产品管理系统

需求分析

开发一个产品管理系统,支持以下功能:

  • 产品的添加、删除、修改、查询。
  • 用户的注册、登录、退出。
  • 产品的分类管理。
  • 产品的搜索功能。
代码实现
模型定义
# myapp/models.py
from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    description = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name
表单定义
# myapp/forms.py
from django import forms
from myapp.models import Product, Category

class CategoryForm(forms.ModelForm):
    class Meta:
        model = Category
        fields = ['name', 'description']

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ['name', 'price', 'description', 'category']
视图函数
# myapp/views.py
from django.shortcuts import render, get_object_or_404, redirect
from myapp.models import Product, Category
from myapp.forms import ProductForm, CategoryForm
from django.contrib.auth.decorators import login_required

def category_list(request):
    categories = Category.objects.all()
    return render(request, 'myapp/category_list.html', {'categories': categories})

def category_detail(request, category_id):
    category = get_object_or_404(Category, pk=category_id)
    products = category.product_set.all()
    return render(request, 'myapp/category_detail.html', {'category': category, 'products': products})

@login_required
def category_create(request):
    if request.method == 'POST':
        form = CategoryForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('category_list')
    else:
        form = CategoryForm()
    return render(request, 'myapp/category_form.html', {'form': form})

@login_required
def category_update(request, category_id):
    category = get_object_or_404(Category, pk=category_id)
    if request.method == 'POST':
        form = CategoryForm(request.POST, instance=category)
        if form.is_valid():
            form.save()
            return redirect('category_list')
    else:
        form = CategoryForm(instance=category)
    return render(request, 'myapp/category_form.html', {'form': form})

@login_required
def category_delete(request, category_id):
    category = get_object_or_404(Category, pk=category_id)
    if request.method == 'POST':
        category.delete()
        return redirect('category_list')
    return render(request, 'myapp/category_delete.html', {'category': category})

def product_list(request):
    search_query = request.GET.get('search', '')
    products = Product.objects.filter(name__icontains=search_query)
    return render(request, 'myapp/product_list.html', {'products': products, 'search_query': search_query})

def product_detail(request, product_id):
    product = get_object_or_404(Product, pk=product_id)
    return render(request, 'myapp/product_detail.html', {'product': product})

@login_required
def product_create(request):
    if request.method == 'POST':
        form = ProductForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('product_list')
    else:
        form = ProductForm()
    return render(request, 'myapp/product_form.html', {'form': form})

@login_required
def product_update(request, product_id):
    product = get_object_or_404(Product, pk=product_id)
    if request.method == 'POST':
        form = ProductForm(request.POST, instance=product)
        if form.is_valid():
            form.save()
            return redirect('product_list')
    else:
        form = ProductForm(instance=product)
    return render(request, 'myapp/product_form.html', {'form': form})

@login_required
def product_delete(request, product_id):
    product = get_object_or_404(Product, pk=product_id)
    if request.method == 'POST':
        product.delete()
        return redirect('product_list')
    return render(request, 'myapp/product_delete.html', {'product': product})
路由配置
# myapp/urls.py
from django.urls import path
from myapp import views

urlpatterns = [
    path('', views.product_list, name='product_list'),
    path('category/', views.category_list, name='category_list'),
    path('category/<int:category_id>/', views.category_detail, name='category_detail'),
    path('category/create/', views.category_create, name='category_create'),
    path('category/<int:category_id>/update/', views.category_update, name='category_update'),
    path('category/<int:category_id>/delete/', views.category_delete, name='category_delete'),
    path('product/<int:product_id>/', views.product_detail, name='product_detail'),
    path('product/create/', views.product_create, name='product_create'),
    path('product/<int:product_id>/update/', views.product_update, name='product_update'),
    path('product/<int:product_id>/delete/', views.product_delete, name='product_delete'),
]
模板定义
<!-- myapp/templates/myapp/category_list.html -->
{% extends 'myapp/base.html' %}
{% block title %}分类列表{% endblock %}
{% block content %}
<h1>分类列表</h1>
<table class="table table-striped">
    <thead>
        <tr>
            <th>名称</th>
            <th>描述</th>
            <th>创建时间</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        {% for category in categories %}
        <tr>
            <td>{{ category.name }}</td>
            <td>{{ category.description }}</td>
            <td>{{ category.created_at }}</td>
            <td>
                <a href="{% url 'category_detail' category.id %}" class="btn btn-primary btn-sm">查看</a>
                <a href="{% url 'category_update' category.id %}" class="btn btn-secondary btn-sm">编辑</a>
                <a href="{% url 'category_delete' category.id %}" class="btn btn-danger btn-sm">删除</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>
<a href="{% url 'category_create' %}" class="btn btn-primary">添加分类</a>
{% endblock %}
<!-- myapp/templates/myapp/category_detail.html -->
{% extends 'myapp/base.html' %}
{% block title %}分类详情{% endblock %}
{% block content %}
<h1>{{ category.name }}</h1>
<p>{{ category.description }}</p>
<h2>产品列表</h2>
<table class="table table-striped">
    <thead>
        <tr>
            <th>名称</th>
            <th>价格</th>
            <th>描述</th>
            <th>创建时间</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        {% for product in products %}
        <tr>
            <td>{{ product.name }}</td>
            <td>{{ product.price }}</td>
            <td>{{ product.description }}</td>
            <td>{{ product.created_at }}</td>
            <td>
                <a href="{% url 'product_detail' product.id %}" class="btn btn-primary btn-sm">查看</a>
                <a href="{% url 'product_update' product.id %}" class="btn btn-secondary btn-sm">编辑</a>
                <a href="{% url 'product_delete' product.id %}" class="btn btn-danger btn-sm">删除</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>
<a href="{% url 'product_create' %}" class="btn btn-primary">添加产品</a>
{% endblock %}
<!-- myapp/templates/myapp/category_form.html -->
{% extends 'myapp/base.html' %}
{% block title %}添加/编辑分类{% endblock %}
{% block content %}
<h1>{% if form.instance.pk %}编辑分类{% else %}添加分类{% endif %}</h1>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">保存</button>
    <a href="{% url 'category_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}
<!-- myapp/templates/myapp/category_delete.html -->
{% extends 'myapp/base.html' %}
{% block title %}删除分类{% endblock %}
{% block content %}
<h1>删除分类</h1>
<p>你确定要删除分类 "{{ category.name }}" 吗?</p>
<form method="post">
    {% csrf_token %}
    <button type="submit" class="btn btn-danger">确认删除</button>
    <a href="{% url 'category_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}
搜索功能
<!-- myapp/templates/myapp/product_list.html -->
{% extends 'myapp/base.html' %}
{% block title %}产品列表{% endblock %}
{% block content %}
<h1>产品列表</h1>
<form method="get">
    <div class="form-group">
        <input type="text" name="search" value="{{ search_query }}" class="form-control" placeholder="搜索产品名称">
    </div>
    <button type="submit" class="btn btn-primary">搜索</button>
</form>
<table class="table table-striped mt-4">
    <thead>
        <tr>
            <th>名称</th>
            <th>价格</th>
            <th>描述</th>
            <th>分类</th>
            <th>创建时间</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        {% for product in products %}
        <tr>
            <td>{{ product.name }}</td>
            <td>{{ product.price }}</td>
            <td>{{ product.description }}</td>
            <td>{{ product.category.name }}</td>
            <td>{{ product.created_at }}</td>
            <td>
                <a href="{% url 'product_detail' product.id %}" class="btn btn-primary btn-sm">查看</a>
                <a href="{% url 'product_update' product.id %}" class="btn btn-secondary btn-sm">编辑</a>
                <a href="{% url 'product_delete' product.id %}" class="btn btn-danger btn-sm">删除</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>
<a href="{% url 'product_create' %}" class="btn btn-primary">添加产品</a>
{% endblock %}
实施过程
  1. 创建 Django 项目和应用。
  2. 定义模型、表单、视图函数。
  3. 配置路由和模板。
  4. 迁移数据库。
  5. 创建超级用户。
  6. 运行开发服务器。
最终效果

通过产品管理系统,我们可以实现以下功能:

  • 产品的添加、删除、修改、查询。
  • 用户的注册、登录、退出。
  • 产品的分类管理。
  • 产品的搜索功能。

总结

本文详细介绍了 Django 框架的核心功能,包括模型、视图、模板、表单、用户认证、权限管理等;通过实战案例开发了一个完整的产品管理系统。建议读者在学习过程中多练习,通过编写代码加深对知识点的理解。

目录

  1. Django Web 框架核心功能与实战
  2. 学习目标与重点
  3. Django 框架概述
  4. 什么是 Django
  5. Django 的特点
  6. Django 的安装
  7. 创建 Django 项目
  8. 创建项目
  9. 创建应用
  10. 项目结构
  11. 模型定义
  12. 定义模型
  13. myapp/models.py
  14. 迁移数据库
  15. 视图函数
  16. 定义视图函数
  17. myapp/views.py
  18. 表单处理
  19. 定义表单
  20. myapp/forms.py
  21. 模板渲染
  22. 定义模板
  23. 路由配置
  24. 应用路由
  25. myapp/urls.py
  26. 项目路由
  27. myproject/urls.py
  28. 用户认证与权限管理
  29. 配置认证系统
  30. myproject/settings.py
  31. 创建用户
  32. 权限管理
  33. myapp/admin.py
  34. 静态文件管理
  35. 配置静态文件
  36. myproject/settings.py
  37. 加载静态文件
  38. 实战案例:产品管理系统
  39. 需求分析
  40. 代码实现
  41. 模型定义
  42. myapp/models.py
  43. 表单定义
  44. myapp/forms.py
  45. 视图函数
  46. myapp/views.py
  47. 路由配置
  48. myapp/urls.py
  49. 模板定义
  50. 搜索功能
  51. 实施过程
  52. 最终效果
  53. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 大模型基于 llama.cpp 量化与 GGUF 格式详解
  • 飞算 Java AI 工具使用指南:安装配置与项目生成
  • 基于 Coze 构建专属 AI 应用:从智能体到 Web 部署实战
  • Sora 2 发布:AI 视频生成技术突破与开发者机遇
  • AI 大模型通信机制:深入理解流式传输与数据封装逻辑
  • CosyVoice3 使用 ARPAbet 音素标注提升英文发音准确率
  • 5 大 AI 代码生成工具实测:DeepSeek-Coder 在测试场景的实战表现
  • Linux 常用命令详解与实战示例
  • 超大型 Oracle 数据库应用系统设计
  • 智能家居 AI 应用变革:从指令执行到主动理解
  • 同向双指针滑动窗口算法:最大连续 1 的个数 III、将 x 减到 0 的最小操作数
  • 大模型微调框架 Firefly 与 LLaMA Factory 对比及选型建议
  • 百度为何不做 Sora:解析其多模态技术路线与 iRAG 策略
  • Linux 上通过 Docker 快速部署 Dify
  • 前端开发与营销业务中的 AI 应用核心趋势解析
  • Cameralink 图像输出和采集时序,针对FPGA开发
  • Windows11 下使用 llama.cpp 本地运行 Qwen3.5 量化模型测试
  • IntelliJ IDEA 切换 JDK 版本完整指南
  • Python 爬虫实战:抓取网易云音乐热歌榜
  • Python 元编程基础:深入理解描述符与装饰器

相关免费在线工具

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online