You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django中如何将两个输入字段合并为一个值存入数据库?

实现方案与优化建议

一、后端处理用户输入生成ETA格式

用户输入分为数值和时间单位,后端需将两者拼接成指定格式存入eta字段,同时建议新增eta_datetime字段存储精确过期时间(大幅简化后续过期判断逻辑)。

1. 模型优化

给Task模型新增两个辅助字段:

from django.db import models
from django.contrib.auth.models import User

class Task(models.Model):
    name = models.CharField(max_length=200, null=True)
    author = models.ForeignKey(User, on_delete=models.DO_NOTHING, blank=True, null=True)
    company = models.ForeignKey(Company, on_delete=models.DO_NOTHING)
    eta = models.CharField(max_length=20, null=True, blank=True)
    eta_datetime = models.DateTimeField(null=True, blank=True)  # 存储精确过期时间
    has_reminded = models.BooleanField(default=False)  # 标记是否已发送提醒

2. 表单处理输入逻辑

以Django表单为例,新增两个输入字段接收数值和单位,在clean流程中完成格式拼接与过期时间计算:

from django import forms
from .models import Task
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta  # 需安装:pip install python-dateutil

class TaskForm(forms.ModelForm):
    ETA_UNITS = [
        ('minutes', '分钟'),
        ('hours', '小时'),
        ('days', '天'),
        ('weeks', '周'),
        ('months', '月'),
    ]
    eta_value = forms.IntegerField(min_value=1, label="ETA数值")
    eta_unit = forms.ChoiceField(choices=ETA_UNITS, label="时间单位")

    class Meta:
        model = Task
        fields = ['name', 'author', 'company']

    def clean(self):
        cleaned_data = super().clean()
        value = cleaned_data.get('eta_value')
        unit = cleaned_data.get('eta_unit')

        # 处理单复数,比如"1 hour"而非"1 hours"
        unit_display = unit[:-1] if value == 1 else unit
        cleaned_data['eta'] = f"{value} {unit_display}"

        # 计算精确过期时间
        now = datetime.now()
        if unit == 'minutes':
            eta_dt = now + timedelta(minutes=value)
        elif unit == 'hours':
            eta_dt = now + timedelta(hours=value)
        elif unit == 'days':
            eta_dt = now + timedelta(days=value)
        elif unit == 'weeks':
            eta_dt = now + timedelta(weeks=value)
        elif unit == 'months':
            # 用relativedelta处理月份更准确,避免按30天计算的误差
            eta_dt = now + relativedelta(months=value)
        cleaned_data['eta_datetime'] = eta_dt

        return cleaned_data

    def save(self, commit=True):
        task = super().save(commit=False)
        task.eta = self.cleaned_data['eta']
        task.eta_datetime = self.cleaned_data['eta_datetime']
        if commit:
            task.save()
        return task

二、ETA过期提醒实现

提供两种主流方案,按需选择:

1. 精准延迟任务(推荐)

用Celery实现延迟任务,在任务创建时直接调度到过期时间触发提醒:

  • 定义Celery任务:
# tasks.py
from celery import shared_task
from django.core.mail import send_mail
from .models import Task

@shared_task
def send_eta_reminder(task_id):
    try:
        task = Task.objects.get(id=task_id)
        if task.author and not task.has_reminded:
            # 发送邮件提醒(可替换为站内信、短信等)
            send_mail(
                subject=f"任务「{task.name}」已过期",
                message=f"你创建的任务「{task.name}」已到达预计完成时间:{task.eta}",
                from_email='noreply@yourdomain.com',
                recipient_list=[task.author.email],
                fail_silently=False,
            )
            # 标记已提醒,避免重复发送
            task.has_reminded = True
            task.save()
    except Task.DoesNotExist:
        pass
  • 在TaskForm的save方法中添加调度逻辑:
from .tasks import send_eta_reminder
import datetime

def save(self, commit=True):
    task = super().save(commit=False)
    task.eta = self.cleaned_data['eta']
    task.eta_datetime = self.cleaned_data['eta_datetime']
    if commit:
        task.save()
        # 计算延迟秒数,调度任务
        delay_seconds = (task.eta_datetime - datetime.datetime.now()).total_seconds()
        if delay_seconds > 0:
            send_eta_reminder.apply_async(args=[task.id], countdown=delay_seconds)
    return task

2. 定时扫描任务

若不想依赖Celery,可通过django-crontab定期扫描数据库:

  • 安装依赖:pip install django-crontab
  • 在settings.py中配置定时任务:
INSTALLED_APPS = [
    # ... 其他应用
    'django_crontab',
]

CRONJOBS = [
    ('*/5 * * * *', 'yourapp.tasks.check_expired_tasks'),  # 每5分钟扫描一次
]
  • 定义扫描任务:
# tasks.py
from datetime import datetime
from django.core.mail import send_mail
from .models import Task

def check_expired_tasks():
    # 筛选已过期且未提醒的任务
    expired_tasks = Task.objects.filter(
        eta_datetime__lte=datetime.now(),
        has_reminded=False
    )
    for task in expired_tasks:
        if task.author:
            send_mail(
                subject=f"任务「{task.name}」已过期",
                message=f"你创建的任务「{task.name}」已到达预计完成时间:{task.eta}",
                from_email='noreply@yourdomain.com',
                recipient_list=[task.author.email],
                fail_silently=False,
            )
            task.has_reminded = True
            task.save()

三、更优方案说明

虽然要求eta字段以CharField存储,但新增eta_datetime字段是核心优化:

  • 避免每次判断过期都需解析字符串(拆分数值和单位的逻辑易出错,尤其是处理月份这类非固定时长的单位)
  • 让过期判断逻辑更直接高效,也方便后续扩展时间相关功能

内容的提问来源于stack exchange,提问作者MesserOr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 14:01:35