Coverage for family/forms.py: 33%

152 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-05 02:45 +0800

1""" 

2Custom forms for Family Knowledge Management System 

3Enhanced forms with family-friendly widgets and validation 

4""" 

5 

6from django import forms 

7from django.core.exceptions import ValidationError 

8from django.utils import timezone 

9from datetime import date, timedelta 

10from .models import ( 

11 Person, Story, Event, Multimedia, Relationship, 

12 Location, Institution, Health, Heritage, Planning 

13) 

14from .widgets import ( 

15 FamilyAutoCompleteWidget, LocationAutoCompleteWidget, 

16 InstitutionAutoCompleteWidget, FamilyDateWidget, 

17 FamilyPhotoWidget, RelationshipSelectorWidget, 

18 RichTextWidget, TagsWidget 

19) 

20 

21 

22class PersonAdminForm(forms.ModelForm): 

23 """ 

24 Enhanced form for Person model with family-friendly widgets 

25 """ 

26 

27 class Meta: 

28 model = Person 

29 fields = '__all__' 

30 widgets = { 

31 'birth_date': FamilyDateWidget(), 

32 'death_date': FamilyDateWidget(), 

33 'photo': FamilyPhotoWidget(), 

34 'bio': RichTextWidget(attrs={'rows': 4}), 

35 } 

36 

37 def __init__(self, *args, **kwargs): 

38 super().__init__(*args, **kwargs) 

39 

40 # Add helpful placeholders and labels 

41 self.fields['name'].widget.attrs.update({ 

42 'placeholder': '请输入姓名', 

43 'class': 'form-control' 

44 }) 

45 

46 self.fields['gender'].widget.attrs.update({ 

47 'class': 'form-control' 

48 }) 

49 

50 self.fields['bio'].widget.attrs.update({ 

51 'placeholder': '在这里记录关于这个人的信息...' 

52 }) 

53 

54 # Add age calculation help text 

55 if self.instance and self.instance.birth_date: 

56 age = self.calculate_age(self.instance.birth_date) 

57 self.fields['birth_date'].help_text = f'当前年龄: {age}岁' 

58 

59 def calculate_age(self, birth_date): 

60 """Calculate age from birth date""" 

61 today = date.today() 

62 return today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day)) 

63 

64 def clean_birth_date(self): 

65 birth_date = self.cleaned_data.get('birth_date') 

66 if birth_date and birth_date > date.today(): 

67 raise ValidationError('出生日期不能是未来的日期') 

68 return birth_date 

69 

70 def clean_death_date(self): 

71 death_date = self.cleaned_data.get('death_date') 

72 birth_date = self.cleaned_data.get('birth_date') 

73 

74 if death_date: 

75 if death_date > date.today(): 

76 raise ValidationError('逝世日期不能是未来的日期') 

77 if birth_date and death_date < birth_date: 

78 raise ValidationError('逝世日期不能早于出生日期') 

79 

80 return death_date 

81 

82 

83class StoryAdminForm(forms.ModelForm): 

84 """ 

85 Enhanced form for Story model with rich text editing 

86 """ 

87 

88 class Meta: 

89 model = Story 

90 fields = '__all__' 

91 widgets = { 

92 'content': RichTextWidget(attrs={'rows': 8}), 

93 'date_occurred': FamilyDateWidget(), 

94 'location': LocationAutoCompleteWidget(), 

95 } 

96 

97 def __init__(self, *args, **kwargs): 

98 super().__init__(*args, **kwargs) 

99 

100 self.fields['title'].widget.attrs.update({ 

101 'placeholder': '给这个故事起个温暖的标题...', 

102 'class': 'form-control' 

103 }) 

104 

105 self.fields['content'].widget.attrs.update({ 

106 'placeholder': '在这里记录您的家族故事...\n\n例如:\n• 那个难忘的春节聚会\n• 爷爷教我下棋的午后\n• 全家一起包饺子的温馨时光' 

107 }) 

108 

109 # Set default date to today 

110 if not self.instance.pk: 

111 self.fields['date_occurred'].initial = date.today() 

112 

113 

114class EventAdminForm(forms.ModelForm): 

115 """ 

116 Enhanced form for Event model with smart date handling 

117 """ 

118 

119 class Meta: 

120 model = Event 

121 fields = '__all__' 

122 widgets = { 

123 'start_date': FamilyDateWidget(), 

124 'end_date': FamilyDateWidget(), 

125 'location': LocationAutoCompleteWidget(), 

126 'description': RichTextWidget(attrs={'rows': 4}), 

127 } 

128 

129 def __init__(self, *args, **kwargs): 

130 super().__init__(*args, **kwargs) 

131 

132 self.fields['name'].widget.attrs.update({ 

133 'placeholder': '活动名称,如:小明生日聚会', 

134 'class': 'form-control' 

135 }) 

136 

137 self.fields['event_type'].widget.attrs.update({ 

138 'class': 'form-control' 

139 }) 

140 

141 # Add future date validation help 

142 self.fields['start_date'].help_text = '可以是过去、今天或未来的日期' 

143 

144 def clean_start_date(self): 

145 start_date = self.cleaned_data.get('start_date') 

146 if start_date: 

147 # Allow past, present and future dates for events 

148 # But warn for very old dates 

149 if start_date.date() < date.today() - timedelta(days=365*10): 

150 # This is just a warning, not an error 

151 pass 

152 return start_date 

153 

154 

155class MultimediaAdminForm(forms.ModelForm): 

156 """ 

157 Enhanced form for Multimedia model with photo preview 

158 """ 

159 

160 class Meta: 

161 model = Multimedia 

162 fields = '__all__' 

163 widgets = { 

164 'file': FamilyPhotoWidget(), 

165 'created_date': FamilyDateWidget(), 

166 'location': LocationAutoCompleteWidget(), 

167 'description': RichTextWidget(attrs={'rows': 3}), 

168 } 

169 

170 def __init__(self, *args, **kwargs): 

171 super().__init__(*args, **kwargs) 

172 

173 self.fields['title'].widget.attrs.update({ 

174 'placeholder': '照片或视频的标题...' 

175 }) 

176 

177 self.fields['description'].widget.attrs.update({ 

178 'placeholder': '描述这张照片的故事...' 

179 }) 

180 

181 # Set default upload date to today 

182 if not self.instance.pk: 

183 self.fields['created_date'].initial = timezone.now() 

184 

185 def clean_file(self): 

186 file = self.cleaned_data.get('file') 

187 if file: 

188 # Check file size (max 10MB) 

189 if file.size > 10 * 1024 * 1024: 

190 raise ValidationError('文件大小不能超过10MB') 

191 

192 # Check file type 

193 allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'video/mp4', 'video/mov'] 

194 if hasattr(file, 'content_type') and file.content_type not in allowed_types: 

195 raise ValidationError('只支持 JPG, PNG, GIF 图片和 MP4, MOV 视频格式') 

196 

197 return file 

198 

199 

200class RelationshipAdminForm(forms.ModelForm): 

201 """ 

202 Enhanced form for Relationship model with visual selector 

203 """ 

204 

205 class Meta: 

206 model = Relationship 

207 fields = '__all__' 

208 widgets = { 

209 'relationship_type': RelationshipSelectorWidget(), 

210 'start_date': FamilyDateWidget(), 

211 'end_date': FamilyDateWidget(), 

212 'description': RichTextWidget(attrs={'rows': 3}), 

213 } 

214 

215 def __init__(self, *args, **kwargs): 

216 super().__init__(*args, **kwargs) 

217 

218 # Add help text for relationship fields 

219 self.fields['person_from'].help_text = '选择关系中的第一个人' 

220 self.fields['person_to'].help_text = '选择关系中的第二个人' 

221 self.fields['relationship_type'].help_text = '选择他们之间的关系类型' 

222 

223 def clean(self): 

224 cleaned_data = super().clean() 

225 person_from = cleaned_data.get('person_from') 

226 person_to = cleaned_data.get('person_to') 

227 

228 if person_from and person_to and person_from == person_to: 

229 raise ValidationError('不能建立一个人与自己的关系') 

230 

231 # Check for duplicate relationships 

232 if person_from and person_to: 

233 existing = Relationship.objects.filter( 

234 person_from=person_from, person_to=person_to 

235 ).exclude(pk=self.instance.pk if self.instance else None) 

236 

237 if existing.exists(): 

238 raise ValidationError('这两个人之间已经存在关系记录') 

239 

240 return cleaned_data 

241 

242 

243class HealthAdminForm(forms.ModelForm): 

244 """ 

245 Enhanced form for Health model with privacy considerations 

246 """ 

247 

248 class Meta: 

249 model = Health 

250 fields = '__all__' 

251 widgets = { 

252 'date': FamilyDateWidget(), 

253 'institution': InstitutionAutoCompleteWidget(institution_type='hospital'), 

254 'description': RichTextWidget(attrs={'rows': 4}), 

255 } 

256 

257 def __init__(self, *args, **kwargs): 

258 super().__init__(*args, **kwargs) 

259 

260 # Add privacy notice 

261 self.fields['description'].help_text = '健康信息将被安全保护,仅限家庭成员查看' 

262 

263 # Set default date to today 

264 if not self.instance.pk: 

265 self.fields['date'].initial = date.today() 

266 

267 

268class LocationAdminForm(forms.ModelForm): 

269 """ 

270 Enhanced form for Location model 

271 """ 

272 

273 class Meta: 

274 model = Location 

275 fields = '__all__' 

276 widgets = { 

277 'description': RichTextWidget(attrs={'rows': 3}), 

278 } 

279 

280 def __init__(self, *args, **kwargs): 

281 super().__init__(*args, **kwargs) 

282 

283 self.fields['name'].widget.attrs.update({ 

284 'placeholder': '地点名称,如:北京市朝阳区' 

285 }) 

286 

287 self.fields['address'].widget.attrs.update({ 

288 'placeholder': '详细地址(可选)' 

289 }) 

290 

291 

292class InstitutionAdminForm(forms.ModelForm): 

293 """ 

294 Enhanced form for Institution model 

295 """ 

296 

297 class Meta: 

298 model = Institution 

299 fields = '__all__' 

300 widgets = { 

301 'location': LocationAutoCompleteWidget(), 

302 'description': RichTextWidget(attrs={'rows': 3}), 

303 } 

304 

305 def __init__(self, *args, **kwargs): 

306 super().__init__(*args, **kwargs) 

307 

308 self.fields['name'].widget.attrs.update({ 

309 'placeholder': '机构名称,如:北京协和医院' 

310 }) 

311 

312 self.fields['institution_type'].widget.attrs.update({ 

313 'class': 'form-control' 

314 }) 

315 

316 

317# Helper functions for form validation 

318 

319def validate_family_photo(image): 

320 """Validate uploaded family photos""" 

321 # Check file size 

322 if image.size > 5 * 1024 * 1024: # 5MB 

323 raise ValidationError('照片文件大小不能超过5MB') 

324 

325 # Check image dimensions 

326 from PIL import Image 

327 img = Image.open(image) 

328 if img.width < 100 or img.height < 100: 

329 raise ValidationError('照片尺寸太小,请上传至少100x100像素的图片') 

330 

331 if img.width > 4000 or img.height > 4000: 

332 raise ValidationError('照片尺寸太大,请上传小于4000x4000像素的图片') 

333 

334 

335def validate_chinese_name(name): 

336 """Validate Chinese names""" 

337 import re 

338 if not re.match(r'^[\u4e00-\u9fff\w\s]+$', name): 

339 raise ValidationError('姓名只能包含中文、英文、数字和空格') 

340 

341 if len(name.strip()) < 2: 

342 raise ValidationError('姓名至少需要2个字符') 

343 

344 if len(name.strip()) > 20: 

345 raise ValidationError('姓名不能超过20个字符') 

346 

347 

348# Form field choices with Chinese labels 

349GENDER_CHOICES = [ 

350 ('', '请选择性别'), 

351 ('male', '男性'), 

352 ('female', '女性'), 

353 ('other', '其他'), 

354] 

355 

356EVENT_TYPE_CHOICES = [ 

357 ('', '请选择事件类型'), 

358 ('birthday', '🎂 生日'), 

359 ('anniversary', '💒 纪念日'), 

360 ('graduation', '🎓 毕业'), 

361 ('wedding', '💍 婚礼'), 

362 ('travel', '✈️ 旅行'), 

363 ('festival', '🎊 节日'), 

364 ('achievement', '🏆 成就'), 

365 ('family_gathering', '👨‍👩‍👧‍👦 家庭聚会'), 

366 ('other', '📝 其他'), 

367] 

368 

369RELATIONSHIP_TYPE_CHOICES = [ 

370 ('', '请选择关系类型'), 

371 ('parent', '父母'), 

372 ('child', '子女'), 

373 ('sibling', '兄弟姐妹'), 

374 ('spouse', '配偶'), 

375 ('grandparent', '祖父母/外祖父母'), 

376 ('grandchild', '孙子女/外孙子女'), 

377 ('uncle_aunt', '叔伯/姑姨'), 

378 ('cousin', '堂兄弟姐妹/表兄弟姐妹'), 

379 ('friend', '朋友'), 

380 ('colleague', '同事'), 

381 ('neighbor', '邻居'), 

382 ('other', '其他'), 

383]