diff --git a/groups/templates/groups/company.html b/groups/templates/groups/company.html index 604bd9d6..c1f7e415 100644 --- a/groups/templates/groups/company.html +++ b/groups/templates/groups/company.html @@ -79,6 +79,9 @@ '', '修改', ' ', + '', + '密码', + ' ', '', diff --git a/safesite/migrations/0336_auto_20200611_2150.py b/safesite/migrations/0336_auto_20200611_2150.py new file mode 100644 index 00000000..b4a19e74 --- /dev/null +++ b/safesite/migrations/0336_auto_20200611_2150.py @@ -0,0 +1,45 @@ +# Generated by Django 2.2.8 on 2020-06-11 21:50 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('safesite', '0335_auto_20200526_2157'), + ] + + operations = [ + migrations.RemoveField( + model_name='equipment', + name='checknum', + ), + migrations.RemoveField( + model_name='equipmentcheckitem', + name='nousecomps', + ), + migrations.RemoveField( + model_name='equipmentcheckitem', + name='usecomps', + ), + migrations.AddField( + model_name='inspect', + name='type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='safesite.Dickey', verbose_name='巡检类型'), + ), + migrations.CreateModel( + name='InspectItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('checked', models.BooleanField(default=True, verbose_name='是否已检查')), + ('state', models.CharField(choices=[('正常', '正常'), ('异常待处理', '异常待处理'), ('异常已处理', '异常已处理')], default='正常', max_length=200, verbose_name='状态')), + ('content', models.TextField(blank=True, null=True, verbose_name='描述')), + ('img', models.TextField(verbose_name='现场图片')), + ('img2', models.TextField(verbose_name='处理后图片')), + ('content2', models.TextField(blank=True, null=True, verbose_name='处理后描述')), + ('checkitem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='safesite.EquipmentCheckItem')), + ('usecomp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='safesite.Partment')), + ], + ), + ] diff --git a/safesite/migrations/0337_auto_20200614_1729.py b/safesite/migrations/0337_auto_20200614_1729.py new file mode 100644 index 00000000..5d81061f --- /dev/null +++ b/safesite/migrations/0337_auto_20200614_1729.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.8 on 2020-06-14 17:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('safesite', '0336_auto_20200611_2150'), + ] + + operations = [ + migrations.RenameField( + model_name='inspectitem', + old_name='content', + new_name='desc', + ), + migrations.RenameField( + model_name='inspectitem', + old_name='content2', + new_name='desc2', + ), + # migrations.RemoveField( + # model_name='companyinfo', + # name='liaison_fax', + # ), + ] diff --git a/safesite/migrations/0338_auto_20200614_2155.py b/safesite/migrations/0338_auto_20200614_2155.py new file mode 100644 index 00000000..ee352cc2 --- /dev/null +++ b/safesite/migrations/0338_auto_20200614_2155.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.8 on 2020-06-14 21:55 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('safesite', '0337_auto_20200614_1729'), + ] + + operations = [ + # migrations.RemoveField( + # model_name='companyinfo', + # name='liaison_fax', + # ), + migrations.RemoveField( + model_name='inspectitem', + name='usecomp', + ), + migrations.AddField( + model_name='inspectitem', + name='inspect', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='safesite.Inspect'), + ), + ] diff --git a/safesite/migrations/0339_auto_20200619_2115.py b/safesite/migrations/0339_auto_20200619_2115.py new file mode 100644 index 00000000..55be3b66 --- /dev/null +++ b/safesite/migrations/0339_auto_20200619_2115.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.8 on 2020-06-19 21:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('safesite', '0338_auto_20200614_2155'), + ] + + operations = [ + migrations.AddField( + model_name='inspectitem', + name='handletime', + field=models.DateTimeField(blank=True, null=True, verbose_name='处理时间'), + ), + migrations.AddField( + model_name='inspectitem', + name='todouser', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='safesite.User'), + ), + ] diff --git a/safesite/models.py b/safesite/models.py index bd800d87..a7a10786 100644 --- a/safesite/models.py +++ b/safesite/models.py @@ -927,11 +927,10 @@ class Equipment(models.Model): # 设备表 checkform = models.ForeignKey(EquipmentCheckForm,on_delete=models.CASCADE,null=True,blank=True) - - class Inspect(models.Model): # 设备巡检记录 id = models.AutoField(primary_key=True) state = models.IntegerField(default=1) # 设备状态 + type = models.ForeignKey(Dickey, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='巡检类型') content = models.TextField(null=True, blank=True) equipment = models.ForeignKey( Equipment, blank=True, null=True, on_delete=models.CASCADE) @@ -944,11 +943,23 @@ class Inspect(models.Model): # 设备巡检记录 trouble = models.ForeignKey( Trouble, on_delete=models.CASCADE, null=True, blank=True) + class InspectItem(models.Model): + state_choices = ( + ('正常', '正常'), + ('异常待处理', '异常待处理'), + ('异常已处理', '异常已处理'), + ) checked = models.BooleanField('是否已检查', default=True) - content = models.TextField('项目描述', null=True, blank=True) - img = models.TextField('现场图片') + state = models.CharField('状态',choices=state_choices, default='正常', max_length=200) + inspect = models.ForeignKey(Inspect, on_delete=models.CASCADE, null=True, blank=True) + todouser = models.ForeignKey(User, on_delete=models.CASCADE, null=True,blank=True) + handletime = models.DateTimeField('处理时间', null=True, blank=True) checkitem = models.ForeignKey(EquipmentCheckItem, on_delete=models.CASCADE) + desc = models.TextField('描述', null=True, blank=True) + img = models.TextField('现场图片') + img2 = models.TextField('处理后图片') + desc2 = models.TextField('处理后描述', null=True, blank=True) class Risk(models.Model): # 风险表 tasktype_choices = ( diff --git a/safesite/serializers.py b/safesite/serializers.py index 3573dba0..dfc655fd 100644 --- a/safesite/serializers.py +++ b/safesite/serializers.py @@ -1,13 +1,18 @@ from .models import * from rest_framework import serializers -class UserSerializers(serializers.ModelSerializer): +class InspectItemSerializer(serializers.ModelSerializer): + class Meta: + model = InspectItem + fields = '__all__' + +class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ('userid','name','username') read_only_fields = ('userid',) -class EquipmentCheckItemSerializers(serializers.ModelSerializer): +class EquipmentCheckItemSerializer(serializers.ModelSerializer): class Meta: model = EquipmentCheckItem fields = '__all__' @@ -16,9 +21,8 @@ class EquipmentCheckFormSerializers(serializers.Serializer): id = serializers.IntegerField(read_only=True) name = serializers.CharField() desc = serializers.CharField() - createby = serializers.PrimaryKeyRelatedField(read_only=True) usecomp = serializers.PrimaryKeyRelatedField(read_only=True) - createby = UserSerializers(read_only=True) + createby = UserSerializer(read_only=True) items = serializers.SerializerMethodField(read_only=True) def create(self, validated_data): items = validated_data.pop('items') diff --git a/safesite/tasks.py b/safesite/tasks.py index 6fee05b7..e1137bd3 100644 --- a/safesite/tasks.py +++ b/safesite/tasks.py @@ -4,14 +4,14 @@ from celery import shared_task import json import logging import requests -from .models import User,Checkjob,Checktask,Trouble,Dickey,Partment,Dicclass,Train,Drill,TroubleAccess,Group,Yjyc,Yjsetup,Socertificate,Trainuser,Risk,Risktask,RiskActTask,Miss,Observe,RiskAct +from .models import User,Checkjob,Checktask,Trouble,Dickey,Partment,Dicclass,Train,Drill,TroubleAccess,Group,Yjyc,Yjsetup,Socertificate,Trainuser,Risk,Risktask,RiskActTask,Miss,Observe,RiskAct, Safecert import datetime import calendar import pandas as pd from sklearn import linear_model from django.db.models import Sum from django.conf import settings -from .safespider import getTzzs +from .safespider import getTzzs, getAqzs dirname = settings.BASE_DIR +'/safesite/' def getcs(companyid):#获取公司相关参数设置 @@ -361,7 +361,8 @@ def checktask(): i.checktime = nowtime i.save() - + +@shared_task def updateTzzs(): for x in Socertificate.objects.all(): data = getTzzs(x.cardnum,x.realname) @@ -380,7 +381,6 @@ def updateTzzs(): 'yxqkssj':i['有效期开始时间'] if i['有效期开始时间'] else None, 'yxqjssj':i['有效期结束时间'] if i['有效期结束时间'] else None, 'sjfssj':i['实际复审时间'] if i['实际复审时间'] else None, - 'url':i['url'], } try: updated_values['zszt'] = guoqi(i['应复审日期']) @@ -412,5 +412,41 @@ def guoqi(x): else: return 3 +@shared_task def updateAqzs(): - pass \ No newline at end of file + for x in Safecert.objects.all(): + data = getAqzs(x.cardnum,x.realname) + data1 = [] + if data: + for i in data: + if i['资格类型'] not in data1: + updated_values={ + 'realname':i['姓名'], + 'gender':i['性别'], + 'zglx':i['资格类型'], + 'dwlx':i['单位类型'], + 'fzjg':i['发证机关'], + 'ccfzrq':i['初领日期'] if i['初领日期'] else None, + 'yfsrq':i['应复审日期'] if i['应复审日期'] else None, + 'yxqkssj':i['有效期开始时间'] if i['有效期开始时间'] else None, + 'yxqjssj':i['有效期结束时间'] if i['有效期结束时间'] else None, + } + try: + updated_values['zszt'] = guoqi(i['有效期结束时间']) + except: + pass + obj, created = Safecert.objects.update_or_create( + cardnum=x.cardnum, zglx=i['资格类型'], dwlx=i['单位类型'], defaults=updated_values) + data1.append(i['资格类型']) + else: + obj = Safecert.objects.get(cardnum=x.cardnum, czxm=i['资格类型']) + if obj.yxqjssj.strftime('%Y-%m-%d')',viewsdrf.EquipmentCheckFormDetailView.as_view()), + path('api/equipmentcheckform',viewsdrf.EquipmentCheckFormAPIView.as_view()), + path('api/examtestrate',views.apiexamtestrate), diff --git a/safesite/views.py b/safesite/views.py index 019670d0..f623cb4c 100644 --- a/safesite/views.py +++ b/safesite/views.py @@ -3,7 +3,7 @@ from captcha.helpers import captcha_image_url from captcha.models import CaptchaStore from django.shortcuts import render, redirect from django.http import HttpResponse, HttpResponseRedirect, JsonResponse -from .models import User, Trouble, Dickey, Partment, Dicclass, Train, Drill, TroubleAccess, Group, Yjyc,Checktable, Trainuser, Drilluser, Yjsetup, Menu, Observe, Observeto, Unsafes, Miss, Socertificate, Userprofile, Suggest, Notice, Noticeto, Operation, Operzyry, Fxcs, Operationspjd, Operspxq, Question, ExamPaper, ExamTest, ExamPaperDetail, ExamTestDetail, Questioncat, Safecert, Map, Area, Missto, Suggestflow, Equipment, Inspect, Risk, RiskAct, Risktask, Riskcheck, Report, RiskActTask, Riskcheck2,Resbility,Operproce,Readerblility,ReaderOperproce,Role,EquipmentCheckForm,EquipmentCheckItem,Checkproject,Checktask,Checkjob +from .models import User, Trouble, Dickey, Partment, Dicclass, Train, Drill, TroubleAccess, Group, Yjyc,Checktable, Trainuser, Drilluser, Yjsetup, Menu, Observe, Observeto, Unsafes, Miss, Socertificate, Userprofile, Suggest, Notice, Noticeto, Operation, Operzyry, Fxcs, Operationspjd, Operspxq, Question, ExamPaper, ExamTest, ExamPaperDetail, ExamTestDetail, Questioncat, Safecert, Map, Area, Missto, Suggestflow, Equipment, Inspect, Risk, RiskAct, Risktask, Riskcheck, Report, RiskActTask, Riskcheck2,Resbility,Operproce,Readerblility,ReaderOperproce,Role,EquipmentCheckForm,EquipmentCheckItem,Checkproject,Checktask,Checkjob,InspectItem from django.template import RequestContext from django.views.decorators.csrf import csrf_exempt from django.core import serializers @@ -6666,7 +6666,7 @@ def apiequipment(req): elif a == 'detail': id = req.GET.get('id') a = Equipment.objects.filter(id=id).values('id', 'num', 'name', 'type', 'oem', 'udate', 'fdate', 'fnum', 'place', 'istz', 'iskey', 'state', 'cate__dickeyname', - 'cate__dickeyid', 'parameter', 'img', 'area__name', 'area__id', 'zrbm__partid', 'zrr__userid', 'zrbm__partname', 'zrr__name', 'qrcode', 'riskact__name', 'riskact__id') + 'cate__dickeyid', 'parameter', 'img', 'area__name', 'area__id', 'zrbm__partid', 'zrr__userid', 'zrbm__partname', 'zrr__name', 'qrcode', 'riskact__name', 'riskact__id', 'checkform') return JsonResponse(a[0]) elif a == 'bindjcb': data = json.loads(req.body.decode('utf-8')) @@ -6685,7 +6685,7 @@ def apiinspect(req): total = a.count() startnum, endnum = fenye(req) a = a.order_by('-id')[startnum:endnum].values('id', 'state', 'content', 'creattime', 'equipment__num', 'equipment__name', - 'equipment__area__name', 'user__name', 'user__ubelongpart__partname', 'trouble__yhzt', 'trouble__yhnum', 'trouble__yhms') + 'equipment__area__name', 'user__name', 'user__ubelongpart__partname', 'trouble__yhzt', 'trouble__yhnum', 'trouble__yhms', 'type__dickeyname') return HttpResponse(transjson(total, a), content_type="application/json") elif a == 'listself': a = Inspect.objects.filter( @@ -6693,7 +6693,7 @@ def apiinspect(req): total = a.count() startnum, endnum = fenye(req) a = a.order_by('-id')[startnum:endnum].values('id', 'state', 'content', 'creattime', 'equipment__num', 'equipment__name', - 'equipment__area__name', 'user__name', 'user__ubelongpart__partname', 'trouble__yhzt', 'trouble__yhnum', 'trouble__yhms') + 'equipment__area__name', 'user__name', 'user__ubelongpart__partname', 'trouble__yhzt', 'trouble__yhnum', 'trouble__yhms', 'type__dickeyname') return HttpResponse(transjson(total, a), content_type="application/json") elif a == 'listsearch': a = Inspect.objects.filter(usecomp__partid=companyid) @@ -6718,7 +6718,7 @@ def apiinspect(req): total = a.count() startnum, endnum = fenye(req) a = a.order_by('-creattime')[startnum:endnum].values('id', 'state', 'content', 'creattime', 'equipment__num', 'equipment__name', - 'equipment__area__name', 'user__name', 'user__ubelongpart__partname', 'trouble__yhzt', 'trouble__yhnum', 'trouble__yhms') + 'equipment__area__name', 'user__name', 'user__ubelongpart__partname', 'trouble__yhzt', 'trouble__yhnum', 'trouble__yhms', 'type__dickeyname') return HttpResponse(transjson(total, a), content_type="application/json") elif a == 'exportexcel': objs = Inspect.objects.filter(usecomp__partid=companyid) @@ -6747,6 +6747,8 @@ def apiinspect(req): a = Inspect() equipment = data['equipment'] state = data['state'] + if 'type' in data: + a.type = Dickey.objects.get(dickeyid=data['type']) if 'content' in data: if data['content']: content = data['content'] @@ -6762,7 +6764,7 @@ def apiinspect(req): a.usecomp = Partment.objects.get(partid=companyid) a.user = User.objects.get(userid=userid) a.save() - return JsonResponse({"code": 1}) + return JsonResponse({"code": 1,"inspect":a.pk}) elif a == 'del': id = req.GET.get('id') user = User.objects.get(userid=userid) @@ -6775,8 +6777,104 @@ def apiinspect(req): elif a == 'detail': id = req.GET.get('id') a = Inspect.objects.filter(id=id).values('id', 'state', 'content', 'creattime', 'equipment__num', 'equipment__id', 'equipment__name', - 'equipment__area__name', 'user__name', 'user__ubelongpart__partname', 'trouble__yhzt', 'trouble__yhnum', 'trouble__yhms', 'trouble__troubleid') - return JsonResponse(a[0]) + 'equipment__area__name', 'user__name', 'user__ubelongpart__partname', 'trouble__yhzt', 'trouble__yhnum', 'trouble__yhms', 'trouble__troubleid', 'type__dickeyname') + return HttpResponse(json.dumps(a[0], cls=MyEncoder), content_type="application/json") + +def apiinspectitem(req): + a = req.GET.get('a') + userid = req.session['userid'] + companyid = getcompany(userid) + if a == 'adds': + data = json.loads(req.body.decode('utf-8')) + for i in data['items']: + obj = InspectItem() + obj.checked = True if 'checked' in i else False + if 'state' in i: + obj.state = i['state'] + obj.checkitem = EquipmentCheckItem.objects.get(pk=i['id']) + obj.inspect = Inspect.objects.get(pk=data['inspect']) + if 'desc' in i: + obj.desc = i['desc'] + if 'img' in i: + obj.img = i['img'] + obj.todouser = getpgr2(userid, User.objects.get(userid=userid).ubelongpart) + obj.save() + postdict = { + 'touser': obj.todouser.openid, + 'template_id': 'lOuwSE67vZC3ZVFYPZvz2eb7JdFxqx7ysMFkXrYmYh0', + 'miniprogram': {'appid': 'wx5c39b569f01c27db'}, + 'data': { + 'first': { + 'value': obj.inspect.equipment.name + '-设备异常待处理:' + }, + 'keyword1': { + 'value': obj.desc + }, + 'keyword2': { + 'value': obj.inspect.creattime + }, + 'keyword3': { + 'value': '' + }, + 'remark': { + 'value': '请及时处理设备异常!' + } + } + } + send_wechatmsg.delay(postdict) + return JsonResponse({"code": 1}) + elif a == 'listmyyc':#我发现的异常记录 + objs = InspectItem.objects.filter(inspect__usecomp__partid=companyid, inspect__user__userid=userid).exclude(state='正常') + total = objs.count() + startnum, endnum = fenye(req) + objs = objs.order_by('-id')[startnum:endnum].values('id', 'state', 'inspect', 'checkitem', 'inspect__equipment__name', 'checkitem__name','inspect__user__name', 'inspect__creattime', 'inspect__equipment__num', 'inspect__type__dickeyname') + return HttpResponse(transjson(total, objs), content_type="application/json") + elif a == 'listtodo':#我待办的异常 + objs = InspectItem.objects.filter(inspect__usecomp__partid=companyid, todouser__userid=userid, state='异常待处理') + total = objs.count() + startnum, endnum = fenye(req) + objs = objs.order_by('-id')[startnum:endnum].values('id', 'state', 'inspect', 'checkitem', 'inspect__equipment__name', 'checkitem__name','inspect__user__name', 'inspect__creattime', 'inspect__equipment__num', 'inspect__type__dickeyname') + return HttpResponse(transjson(total, objs), content_type="application/json") + elif a == 'listdone':#我已处理的异常 + objs = InspectItem.objects.filter(inspect__usecomp__partid=companyid, todouser__userid=userid, state='异常已处理') + total = objs.count() + startnum, endnum = fenye(req) + objs = objs.order_by('-id')[startnum:endnum].values('id', 'state', 'inspect', 'checkitem', 'inspect__equipment__name', 'checkitem__name','inspect__user__name', 'inspect__creattime', 'inspect__equipment__num', 'inspect__type__dickeyname','todouser__name') + return HttpResponse(transjson(total, objs), content_type="application/json") + elif a == 'listall':#我已处理的异常 + objs = InspectItem.objects + if req.GET.get('inspect'): + objs = objs.filter(inspect__id=req.GET.get('inspect')) + total = objs.count() + if req.GET.get('pageoff'): + objs = objs.order_by('-id') + else: + startnum, endnum = fenye(req) + objs = objs.order_by('-id')[startnum:endnum] + objs = objs.values('id', 'state', 'inspect', 'checkitem', 'inspect__equipment__name', 'checkitem__name','checkitem__content','inspect__user__name', 'inspect__creattime', 'inspect__equipment__num', 'inspect__type__dickeyname','todouser__name') + return HttpResponse(transjson(total, objs), content_type="application/json") + elif a == 'detail': + id = req.GET.get('id') + a = InspectItem.objects.filter(id=id).values('id', 'state', 'inspect', 'checkitem', 'inspect__equipment__name', 'inspect__equipment__id', 'checkitem__name', 'checkitem__content','inspect__user__name', 'inspect__creattime', 'inspect__equipment__num', 'inspect__type__dickeyname', + 'img', 'desc', 'img2', 'desc2', 'todouser__name', 'handletime') + return HttpResponse(json.dumps(a[0], cls=MyEncoder), content_type="application/json") + elif a == 'handle': + data = json.loads(req.body.decode('utf-8')) + obj = InspectItem.objects.get(id=data['id']) + obj.desc2 = data['desc2'] + obj.img2 = data['img2'] if ('img2' in data and data['img2']) else None + obj.handletime = datetime.now() + obj.state = '异常已处理' + if InspectItem.objects.filter(inspect__equipment=obj.inspect.equipment,state='异常待处理').exists(): + obj.inspect.equipment.state = 0 + obj.inspect.equipment.save() + else: + obj.inspect.equipment.state = 1 + obj.inspect.equipment.save() + obj.save() + return JsonResponse({'code': 1}) + + def apiriskact(req): diff --git a/safesite/viewsdrf.py b/safesite/viewsdrf.py index 7f7d33dd..c940015a 100644 --- a/safesite/viewsdrf.py +++ b/safesite/viewsdrf.py @@ -11,7 +11,7 @@ class EquipmentCheckFormAPIView(APIView): usecomp = User.objects.get(userid=userid).usecomp queryset = EquipmentCheckForm.objects.filter(deletemark=1,usecomp=usecomp).order_by('-pk') total = queryset.count() - serializer = EquipmentCheckFormSerializers(instance=queryset,many=True) + serializer = EquipmentCheckItemSerializer(instance=queryset,many=True) ret = {'total':total,'rows':serializer.data} return Response(ret) @@ -22,12 +22,12 @@ class EquipmentCheckFormAPIView(APIView): id = request.data.get('id',0) instance = EquipmentCheckForm.objects.filter(id=id) reqdata = request.data - serializer = EquipmentCheckFormSerializers(data=reqdata) + serializer = EquipmentCheckItemSerializer(data=reqdata) if serializer.is_valid(): if instance: serializer.update(instance,reqdata) for i in request.data['items']: - serializerx = EquipmentCheckItemSerializers(data=i) + serializerx = EquipmentCheckItemSerializer(data=i) if serializerx.is_valid(): ins = EquipmentCheckItem.objects.filter(id=i['id']) if ins: @@ -44,7 +44,7 @@ class EquipmentCheckFormAPIView(APIView): else: forminstance = serializer.create(reqdata) for i in request.data['items']: - serializerx = EquipmentCheckItemSerializers(data=i) + serializerx = EquipmentCheckItemSerializer(data=i) if 'id' in i and i['id']: ins = EquipmentCheckItem.objects.filter(id=i['id']).first() if serializerx.is_valid(): @@ -61,4 +61,4 @@ class EquipmentCheckFormAPIView(APIView): class EquipmentCheckFormDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentCheckForm.objects.all() serializer_class = EquipmentCheckFormSerializers - +