Django入门到放弃之forms组件
1 2 3 4 5 6 7 8 | 1 注册功能,登录功能,前端需要校验(字段长度,邮箱是否合法。。。) 2 前端校验可以没有,后端校验是必须的,使用传统方式 if 判断写的很多 3 借助于forms组件,可以快速实现字段的校验 from django.forms import Form 总结一下,其实form组件的主要功能如下: 生成页面可用的HTML标签 对用户提交的数据进行校验 保留上次输入内容 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | Field required = True , 是否允许为空 widget = None , HTML插件 label = None , 用于生成Label标签或显示内容 initial = None , 初始值 help_text = '', 帮助信息(在标签旁边显示) error_messages = None , 错误信息 { 'required' : '不能为空' , 'invalid' : '格式错误' } validators = [], 自定义验证规则 localize = False , 是否支持本地化 disabled = False , 是否可以编辑 label_suffix = None Label内容后缀 CharField(Field) max_length = None , 最大长度 min_length = None , 最小长度 strip = True 是否移除用户输入空白 IntegerField(Field) max_value = None , 最大值 min_value = None , 最小值 FloatField(IntegerField) ... DecimalField(IntegerField) max_value = None , 最大值 min_value = None , 最小值 max_digits = None , 总长度 decimal_places = None , 小数位长度 BaseTemporalField(Field) input_formats = None 时间格式化 DateField(BaseTemporalField) 格式: 2015 - 09 - 01 TimeField(BaseTemporalField) 格式: 11 : 12 DateTimeField(BaseTemporalField)格式: 2015 - 09 - 01 11 : 12 DurationField(Field) 时间间隔: % d % H: % M: % S. % f ... RegexField(CharField) regex, 自定制正则表达式 max_length = None , 最大长度 min_length = None , 最小长度 error_message = None , 忽略,错误信息使用 error_messages = { 'invalid' : '...' } EmailField(CharField) ... FileField(Field) allow_empty_file = False 是否允许空文件 ImageField(FileField) ... 注:需要PIL模块,pip3 install Pillow 以上两个字典使用时,需要注意两点: - form表单中 enctype = "multipart/form-data" - view函数中 obj = MyForm(request.POST, request.FILES) URLField(Field) ... BooleanField(Field) ... NullBooleanField(BooleanField) ... ChoiceField(Field) ... choices = (), 选项,如:choices = (( 0 , '上海' ),( 1 , '北京' ),) required = True , 是否必填 widget = None , 插件,默认select插件 label = None , Label内容 initial = None , 初始值 help_text = '', 帮助提示 ModelChoiceField(ChoiceField) ... django.forms.models.ModelChoiceField queryset, # 查询数据库中的数据 empty_label = "---------" , # 默认空显示内容 to_field_name = None , # HTML中value的值对应的字段 limit_choices_to = None # ModelForm中对queryset二次筛选 ModelMultipleChoiceField(ModelChoiceField) ... django.forms.models.ModelMultipleChoiceField TypedChoiceField(ChoiceField) coerce = lambda val: val 对选中的值进行一次转换 empty_value = '' 空值的默认值 MultipleChoiceField(ChoiceField) ... TypedMultipleChoiceField(MultipleChoiceField) coerce = lambda val: val 对选中的每一个值进行一次转换 empty_value = '' 空值的默认值 ComboField(Field) fields = () 使用多个验证,如下:即验证最大长度 20 ,又验证邮箱格式 fields.ComboField(fields = [fields.CharField(max_length = 20 ), fields.EmailField(),]) MultiValueField(Field) PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 SplitDateTimeField(MultiValueField) input_date_formats = None , 格式列表:[ '%Y--%m--%d' , '%m%d/%Y' , '%m/%d/%y' ] input_time_formats = None 格式列表:[ '%H:%M:%S' , '%H:%M:%S.%f' , '%H:%M' ] FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 path, 文件夹路径 match = None , 正则匹配 recursive = False , 递归下面的文件夹 allow_files = True , 允许文件 allow_folders = False , 允许文件夹 required = True , widget = None , label = None , initial = None , help_text = '' GenericIPAddressField protocol = 'both' , both,ipv4,ipv6支持的IP格式 unpack_ipv4 = False 解析ipv4地址,如果是::ffff: 192.0 . 2.1 时候,可解析为 192.0 . 2.1 , PS:protocol必须为both才能启用 SlugField(CharField) 数字,字母,下划线,减号(连字符) ... UUIDField(CharField) uuid类型 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | ### 1 写一个类,类里写要校验的字段 class MyForm(forms.Form): # 校验这个字段,最大长度是32,最小长度是3 # required=Ture表示不能为空 为False表示可以为空,但是如果有值则进行校验 name = forms.CharField(required = False , max_length = 32 , min_length = 3 ,label = '用户名' ) email = forms.EmailField(label = '邮箱' ) age = forms.IntegerField(max_value = 200 ,min_value = 0 ,label = '年龄' ) ### 2 视图函数中使用 def register(request): # 数据可以是从前端传过来的,也可以是自己后台的数据 # 我现在有以下数据 data = { 'name' : 'lqz' , 'email' : '33333@qq.com' , 'age' : 900 } # data={'email':'33333@qq.com','age':100} # data={'age':100} # 校验数据是否合法 # 实例化得到form对象,把要校验的数据传入 form = myforms.MyForm(data) # 校验数据:form.is_valid() 返回布尔类型 if form.is_valid(): print ( '校验通过' ) # 校验通过的数据 print (form.cleaned_data) #无论是否校验通过,都可以获取 cleaned_data,但是必须在is_valid之后 else : print (form.cleaned_data) # form.cleaned_data 不一定是上面传入的数据,只包含校验通的的字段 print ( '校验失败' ) # 哪个字段失败了?失败的原因是什么 print (form.errors) print ( type (form.errors)) from django.forms.utils import ErrorDict #### 重写了__str__ print (form.errors.as_json()) #可以返回多种不同格式的数据 print (form.errors.as_data()) # form.errors.as_ul() # 是为了渲染模板 return HttpResponse( 'ok' ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | # 定制模板中的显示样式,及配置类 # widget=widgets.PasswordInput(attrs={'class': 'form-control'}) # 错误信息中文显示 error_messages = { 'min_length' : '太短了小伙子' } from django import forms from django.forms import widgets from django.forms import ValidationError class Myform(forms.Form): name = forms.CharField(required = False , max_length = 32 , min_length = 3 , label = '用户名' , widget = widgets.TextInput(attrs = { 'class' : 'form-control' }), error_messages = { 'min_length' : '太短了小伙子' }) password = forms.CharField(required = False , max_length = 32 , min_length = 3 , label = '密码' , widget = widgets.PasswordInput(attrs = { 'class' : 'form-control' }), error_messages = { 'min_length' : '太短了小伙子' }) re_password = forms.CharField(required = False , max_length = 32 , min_length = 3 , label = '确认密码' , widget = widgets.PasswordInput(attrs = { 'class' : 'form-control' }), error_messages = { 'min_length' : '太短了小伙子' }) email = forms.EmailField(label = '邮箱' , error_messages = { 'required' : '小惠子,这个必填' }, widget = widgets.TextInput(attrs = { 'class' : 'form-control' })) age = forms.IntegerField(max_value = 200 , min_value = 0 , label = '年龄' , widget = widgets.TextInput(attrs = { 'class' : 'form-control' })) text = forms.CharField(label = '个人简介' , widget = widgets.Textarea(attrs = { 'class' : 'form-control' })) date = forms.DateField(label = '日期' , widget = forms.DateInput(attrs = { 'type' : 'date' , 'class' : 'form-control' })) def clean_name( self ): name = self .cleaned_data.get( 'name' ) if name.startswith( 'sb' ): raise ValidationError( '不能以sb开头' ) else : return name def clean( self ): password = self .cleaned_data.get( 'password' ) re_password = self .cleaned_data.get( 're_password' ) if password = = re_password: return self .cleaned_data else : raise ValidationError( '两次密码不一致' ) class LgoinForm(forms.Form): name = forms.CharField( required = True , strip = True , help_text = '不能小于六位' , max_length = 16 , initial = '狗剩' , min_length = 6 , label = "用户名" , error_messages = { "max_length" : "太长了" , "min_length" : "太短了了" , }, widget = widgets.TextInput(attrs = { 'class' : 'form-control' }) ) password = forms.CharField( max_length = 16 , min_length = 6 , label = "密码" , widget = widgets.PasswordInput(attrs = { 'class' : 'form-control' }) ) sex = forms.ChoiceField( label = '性别' , initial = 3 , choices = (( 1 , '男' ),( 2 , '女' ),( 3 , '保密' )), widget = widgets.RadioSelect() ) city = forms.ChoiceField( label = '籍贯' , initial = 1 , choices = (( 1 , '上海' ), ( 2 , '北京' ), ( 3 , '芜湖' )), widget = widgets.Select() ) hobby = forms.MultipleChoiceField( label = '爱好' , initial = [ 1 , 3 ], choices = (( 1 , '抽烟' ), ( 2 , '喝酒' ), ( 3 , '烫头' )), widget = widgets.CheckboxSelectMultiple, ) girls = forms.MultipleChoiceField( label = '女朋友' , choices = (( 1 , '红旭妹妹' ), ( 2 , '相熙哥哥' ), ( 3 , '程根姐姐' )), widget = widgets.SelectMultiple, ) status = forms.ChoiceField( label = 'remeber me' , choices = (( 'True' , '红旭妹妹' ),( 'False' , '程根姐姐' )), widget = widgets.CheckboxInput ) birthday = forms.CharField( label = '生日' , widget = widgets.TextInput(attrs = { 'type' : 'date' }) ) |
1 2 3 4 5 6 7 8 9 | from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.validators import RegexValidator class MyForm(Form): user = fields.CharField( validators = [RegexValidator(r '^[0-9]+$' , '请输入数字' ), RegexValidator(r '^159[0-9]+$' , '数字必须以159开头' )], ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import re from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.exceptions import ValidationError # 自定义验证规则 def mobile_validate(value): mobile_re = re. compile (r '^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$' ) if not mobile_re.match(value): raise ValidationError( '手机号码格式错误' ) #自定义验证规则的时候,如果不符合你的规则,需要自己发起错误 class PublishForm(Form): title = fields.CharField(max_length = 20 , min_length = 5 , error_messages = { 'required' : '标题不能为空' , 'min_length' : '标题最少为5个字符' , 'max_length' : '标题最多为20个字符' }, widget = widgets.TextInput(attrs = { 'class' : "form-control" , 'placeholder' : '标题5-20个字符' })) # 使用自定义验证规则 phone = fields.CharField(validators = [mobile_validate, ], error_messages = { 'required' : '手机不能为空' }, widget = widgets.TextInput(attrs = { 'class' : "form-control" , 'placeholder' : u '手机号码' })) email = fields.EmailField(required = False , error_messages = { 'required' : u '邮箱不能为空' , 'invalid' : u '邮箱格式错误' }, widget = widgets.TextInput(attrs = { 'class' : "form-control" , 'placeholder' : u '邮箱' })) |
1 2 3 4 5 6 7 8 9 10 | from django import forms # 写一个类,写字段 class MyForm(forms.Form): # 校验这个字段,最大长度是32,最小长度是3 # required=Trues时字段允许不传,如果传则必须符合校验 name = forms.CharField(required = False , max_length = 32 , min_length = 3 ,label = '用户名' ) email = forms.EmailField(label = '邮箱' ) age = forms.IntegerField(max_value = 200 ,min_value = 0 ,label = '年龄' ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from app01 import myforms def register(request): if request.method = = 'GET' : form = myforms.MyForm() #获取form return render(request, 'register.html' ,{ 'form' :form}) elif request.method = = 'POST' : # 数据校验 form = myforms.MyForm(request.POST) if form.is_valid(): print ( '校验通过,存数据库' ) else : print (form.errors.as_data()) print ( '校验失败,返回错误' ) return HttpResponse( 'ok' ) |
1 2 3 4 5 6 7 | <h1>手动创建模板< / h1> <form action = " " method=" post"> <p>用户名:< input type = "text" name = "name" >< / p> <p>邮箱:< input type = "text" name = "email" >< / p> <p>年龄:< input type = "text" name = "age" >< / p> <p>< input type = "submit" value = "提交" >< / p> < / form> |
1 2 3 4 5 6 7 | <h1>半自动渲染模板 1 < / h1> <form action = " " method=" post"> <p>用户名:{{ form.name }}< / p> <p>邮箱:{{ form.email }}< / p> <p>年龄:{{ form.age }}< / p> <p>< input type = "submit" value = "提交" >< / p> < / form> |
1 2 3 4 5 6 7 | <h1>半自动渲染模板 2 < / h1> <form action = " " method=" post"> <p>{{ form.name.label }} - - {{ form.name }}< / p> <p>{{ form.email.label }} - - - {{ form.email }}< / p> <p>{{ form.age.label }} - - - {{ form.age }}< / p> <p>< input type = "submit" value = "提交" >< / p> < / form> |
1 2 3 4 5 6 7 8 | <h1>半自动渲染模板 3 (用的最多)< / h1> <form action = " " method=" post"> { % for foo in form % } <p>{{ foo.label }} :{{ foo }}< / p> { % endfor % } <p>< input type = "submit" value = "提交" >< / p> < / form> |
1 2 3 4 5 6 7 8 9 10 | <h1>全自动(了解)< / h1> <form action = " " method=" post"> { # {{ form.as_ul }}#} {{ form.as_p }} { # <table>#} { # {{ form.as_table }}#} { # </table>#} <p>< input type = "submit" value = "提交" >< / p> < / form> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 1 form对象.errors 字典 2 name对象.errors ## 视图函数 def register(request): if request.method = = 'GET' : form = myforms.MyForm() return render(request, 'register.html' ,{ 'form' :form}) else : form = myforms.MyForm(request.POST) if form.is_valid(): return redirect( 'http://www.baidu.com' ) else : return render(request, 'register.html' ,{ 'form' :form}) ## 模板 <form action = " " method=" post" novalidate> { % for foo in form % } <div class = "form-group" > <label for = "">{{ foo.label }}< / label> {{ foo }} # foo.errors 每一个form字段的错误 <span class = "text-danger pull-right" >{{ foo.errors }}< / span> < / div> { % endfor % } <div class = "text-center" > < input type = "submit" value = "提交" class = "btn btn-danger" > # form.errors 所有的报错信息 <span>{{ form.errors }}< / span> < / div> < / form> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 对特定字段进行校验 class LoginForm(forms.Form): username = forms.CharField( min_length = 8 , label = "用户名" , initial = "张三" , error_messages = { "required" : "不能为空" , "invalid" : "格式错误" , "min_length" : "用户名最短8位" }, widget = forms.widgets.TextInput(attrs = { "class" : "form-control" }) ) ... # 定义局部钩子clean_fieldName,用来校验username字段,之前的校验股则还在,给你提供了一个添加一些校验功能的钩子 def clean_username( self ): value = self .cleaned_data.get( "username" ) if "666" in value: raise ValidationError( "光喊666是不行的" ) else : return value |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class LoginForm(forms.Form): ... password = forms.CharField( min_length = 6 , label = "密码" , widget = forms.widgets.PasswordInput(attrs = { 'class' : 'form-control' }, render_value = True ) ) re_password = forms.CharField( min_length = 6 , label = "确认密码" , widget = forms.widgets.PasswordInput(attrs = { 'class' : 'form-control' }, render_value = True ) ) ... # 定义全局的钩子,用来校验密码和确认密码字段是否相同,执行全局钩子的时候,cleaned_data里面肯定是有了通过前面验证的所有数据 def clean( self ): password_value = self .cleaned_data.get( 'password' ) re_password_value = self .cleaned_data.get( 're_password' ) if password_value = = re_password_value: return self .cleaned_data #全局钩子要返回所有的数据 else : self .add_error( 're_password' , '两次密码不一致' ) #在re_password这个字段的错误列表中加上一个错误,并且clean_data里面会自动清除这个re_password的值,所以打印clean_data的时候会看不到它 raise ValidationError( '两次密码不一致' ) # form.errors.get('__all__') python中获取全局错误 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #首先导入ModelForm from django.forms import ModelForm #在视图函数中,定义一个类,比如就叫StudentList,这个类要继承ModelForm,在这个类中再写一个原类Meta(规定写法,并注意首字母是大写的) #在这个原类中,有以下属性(部分): class StudentList(ModelForm): class Meta: model = Student #对应的Model中的类 fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段 exclude = None #排除的字段 #error_messages用法: error_messages = { 'name' :{ 'required' : "用户名不能为空" ,}, 'age' :{ 'required' : "年龄不能为空" ,}, } #widgets用法,比如把输入用户名的input框给为Textarea #首先得导入模块 from django.forms import widgets as wid #因为重名,所以起个别名 widgets = { "name" :wid.Textarea(attrs = { "class" : "c1" }) #还可以自定义属性 } #labels,自定义在前端显示的名字 labels = { "name" : "用户名" } 然后在url对应的视图函数中实例化这个类,把这个对象传给前端 def student(request): if request.method = = 'GET' : student_list = StudentList() return render(request, 'student.html' ,{ 'student_list' :student_list}) 然后前端只需要 {{ student_list.as_p }} 一下,所有的字段就都出来了,可以用as_p显示全部,也可以通过 for 循环 <body> <div class = "container" > <h1>student< / h1> <form method = "POST" novalidate> { % csrf_token % } { # {{ student_list.as_p }}#} { % for student in student_list % } <div class = "form-group col-md-6" > { # 拿到数据字段的verbose_name,没有就默认显示字段名 #} <label class = "col-md-3 control-label" >{{ student.label }}< / label> <div class = "col-md-9" style = "position: relative;" >{{ student }}< / div> < / div> { % endfor % } <div class = "col-md-2 col-md-offset-10" > < input type = "submit" value = "提交" class = "btn-primary" > < / div> < / form> < / div> < / body> |
1 2 3 4 5 6 7 8 9 10 | def student(request): if request.method = = 'GET' : student_list = StudentList() return render(request, 'student.html' ,{ 'student_list' :student_list}) else : student_list = StudentList(request.POST) if student_list.is_valid(): student_list.save() return redirect(request, 'student_list.html' ,{ 'student_list' :student_list} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | from django.shortcuts import render,HttpResponse,redirect from django.forms import ModelForm # Create your views here. from app01 import models def test(request): # model_form = models.Student model_form = models.Student.objects. all () return render(request, 'test.html' ,{ 'model_form' :model_form}) class StudentList(ModelForm): class Meta: model = models.Student #对应的Model中的类 fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段 exclude = None #排除的字段 labels = None #提示信息 help_texts = None #帮助提示信息 widgets = None #自定义插件 error_messages = None #自定义错误信息 #error_messages用法: error_messages = { 'name' :{ 'required' : "用户名不能为空" ,}, 'age' :{ 'required' : "年龄不能为空" ,}, } #widgets用法,比如把输入用户名的input框给为Textarea #首先得导入模块 from django.forms import widgets as wid #因为重名,所以起个别名 widgets = { "name" :wid.Textarea } #labels,自定义在前端显示的名字 labels = { "name" : "用户名" } def student(request): if request.method = = 'GET' : student_list = StudentList() return render(request, 'student.html' ,{ 'student_list' :student_list}) else : student_list = StudentList(request.POST) if student_list.is_valid(): student_list.save() return render(request, 'student.html' ,{ 'student_list' :student_list}) def student_edit(request,pk): obj = models.Student.objects. filter (pk = pk).first() if not obj: return redirect( 'test' ) if request.method = = "GET" : student_list = StudentList(instance = obj) return render(request, 'student_edit.html' ,{ 'student_list' :student_list}) else : student_list = StudentList(request.POST,instance = obj) if student_list.is_valid(): student_list.save() return render(request, 'student_edit.html' ,{ 'student_list' :student_list}) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | def full_clean( self ): """ Clean all of self.data and populate self._errors and self.cleaned_data. """ self ._errors = ErrorDict() if not self .is_bound: # Stop further processing. return self .cleaned_data = {} # If the form is permitted to be empty, and none of the form data has # changed from the initial data, short circuit any validation. if self .empty_permitted and not self .has_changed(): return # 为什么全局钩子是在以上所有校验通过才走 self ._clean_fields() 局部钩子 self ._clean_form() 全句钩子 self ._post_clean() def _clean_fields( self ): for name, field in self .fields.items(): # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. if field.disabled: value = self .get_initial_for_field(field, name) else : value = field.widget.value_from_datadict( self .data, self .files, self .add_prefix(name)) try : # 字段自己的校验完成后才执行局部钩子 if isinstance (field, FileField): initial = self .get_initial_for_field(field, name) value = field.clean(value, initial) else : value = field.clean(value) self .cleaned_data[name] = value # 为什么名字一定要叫clean_字段名, if hasattr ( self , 'clean_%s' % name): value = getattr ( self , 'clean_%s' % name)() #执行局部钩子 # 为什么成功要把字段值返回, self .cleaned_data[name] = value # 把局部钩子的返回结果覆盖到字段执行结果中 # 为失败抛出ValidationError except ValidationError as e: self .add_error(name, e) #如果发生错误,给字段添加错误 def _clean_form( self ): try : cleaned_data = self .clean() except ValidationError as e: self .add_error( None , e) else : if cleaned_data is not None : # 为什么在视图函数中拿到的就是返回的 self .cleaned_data = cleaned_data #如果全局钩子有返回则覆盖所有返回内容 为什么全局钩子返回一个自己写的字典 ? |
"一劳永逸" 的话,有是有的,而 "一劳永逸" 的事却极少
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
2020-08-27 Linux 五种IO模型