def PrjEditPriv(PrjId): Prj=database.Projects.query.filter_by(projid=PrjId).first() g.headcenter="<h4><a href='/prj/{0}'>{1}</a></h4>".format(Prj.projid,XSSEscape(Prj.title)) if Prj is None: flash("Project doesn't exists",'error') return PrintInCharte("<a href=/prj/>Select another project</a>") if not Prj.CheckRight(2): # Level 0 = Read, 1 = Annotate, 2 = Admin flash('You cannot edit settings for this project','error') return PrintInCharte("<a href=/prj/>Select another project</a>") if gvp('save')=="Y": # print(request.form) for m in Prj.projmembers: if gvp('priv_%s_delete'%m.id)=='Y': db.session.delete(m) elif gvp('priv_%s_member'%m.id)!='': # si pas delete c'est update m.member=int(gvp('priv_%s_member'%m.id)) m.privilege=gvp('priv_%s_privilege'%m.id) if gvp('priv_new_member')!='': new=database.ProjectsPriv(member=int(gvp('priv_new_member')),privilege=gvp('priv_new_privilege'),projid=PrjId) db.session.add(new) try: db.session.commit() flash("Project settings Saved successfuly","success") except Exception as E: flash("Database exception : %s"%E,"error") db.session.rollback() g.users=GetAssoc2Col("select id,name from users order by lower(name)",dicttype=collections.OrderedDict) return render_template('project/editprojectpriv.html',data=Prj)
def PrjEdit(PrjId): g.useselect4 = True Prj = database.Projects.query.filter_by(projid=PrjId).first() g.headcenter = "<h4><a href='/prj/{0}'>{1}</a></h4>".format( Prj.projid, XSSEscape(Prj.title)) if Prj is None: flash("Project doesn't exists", 'error') return PrintInCharte("<a href=/prj/>Select another project</a>") if not Prj.CheckRight(2): # Level 0 = Read, 1 = Annotate, 2 = Admin flash('You cannot edit settings for this project', 'error') return PrintInCharte("<a href=/prj/>Select another project</a>") if gvp('save') == "Y": PreviousCNN = Prj.cnn_network_id for f in request.form: if f in dir(Prj): setattr(Prj, f, gvp(f)) if PreviousCNN != Prj.cnn_network_id: database.ExecSQL( "delete from obj_cnn_features where objcnnid in (select objid from obj_head where projid=%s)", [PrjId]) flash("SCN features erased", "success") Prj.visible = True if gvp('visible') == 'Y' else False # print(request.form) for m in Prj.projmembers: if gvp('priv_%s_delete' % m.id) == 'Y': db.session.delete(m) elif gvp('priv_%s_member' % m.id) != '': # si pas delete c'est update m.member = int(gvp('priv_%s_member' % m.id)) m.privilege = gvp('priv_%s_privilege' % m.id) if gvp('priv_new_member') != '': new = database.ProjectsPriv(member=int(gvp('priv_new_member')), privilege=gvp('priv_new_privilege'), projid=PrjId) db.session.add(new) try: db.session.commit() flash("Project settings Saved successfuly", "success") except Exception as E: flash("Database exception : %s" % E, "error") db.session.rollback() if Prj.initclassiflist is None: lst = [] else: lst = [int(x) for x in Prj.initclassiflist.split(",") if x.isdigit()] g.predeftaxo = GetAll( """select t.id,t.display_name as name from taxonomy t left join taxonomy t2 on t.parent_id=t2.id where t.id= any(%s) order by upper(t.display_name) """, (lst, )) g.users = GetAssoc2Col("select id,name from users order by lower(name)", dicttype=collections.OrderedDict) g.maplist = [ 'objtime', 'objdate', 'latitude', 'longitude', 'depth_min', 'depth_max' ] + sorted(DecodeEqualList(Prj.mappingobj).values()) g.scn = GetSCNNetworks() return render_template('project/editproject.html', data=Prj)
def QuestionProcess(self): if not (current_user.has_role(database.AdministratorLabel) or current_user.has_role(database.ProjectCreatorLabel)): return PrintInCharte( "ACCESS DENIED for this feature, Admin or privilege creation Required" ) ServerRoot = Path(app.config['SERVERLOADAREA']) txt = "<h1>Database Importation Task</h1>" errors = [] if gvg("restart") == 'Y': # force redemarrage self.task.taskstep = 1 self.UpdateParam() if self.task.taskstep == 0: # ################## Question Creation txt += "<h3>Task Creation</h3>" if gvp('starttask') == "Y": FileToSave = None FileToSaveFileName = None # Verifier la coherence des données uploadfile = request.files.get("uploadfile") if uploadfile is not None and uploadfile.filename != '': # import d'un fichier par HTTP FileToSave = uploadfile # La copie est faite plus tard, car à ce moment là, le repertoire de la tache n'est pas encore créé FileToSaveFileName = "uploaded.zip" self.param.InData = "uploaded.zip" elif len(gvp("ServerPath")) < 2: errors.append("Input Folder/File Too Short") else: sp = ServerRoot.joinpath(Path(gvp("ServerPath"))) if not sp.exists(): #verifie que le repertoire existe errors.append("Input Folder/File Invalid") else: self.param.InData = sp.as_posix() if len(errors) > 0: for e in errors: flash(e, "error") else: return self.StartTask( self.param, FileToSave=FileToSave, FileToSaveFileName=FileToSaveFileName) return render_template('task/importdb_create.html', header=txt, data=self.param, ServerPath=gvp("ServerPath")) newschema = self.GetWorkingSchema() # self.task.taskstep=1 if self.task.taskstep == 1: # ################## Question Post Import DB if gvg("src") == "": # il faut choisir le projet source txt += "<h3>Select project to import</h3>" PrjList = GetAll( "select projid,title,status from {0}.projects order by lower(title)" .format(newschema), cursor_factory=None) txt += """<table class='table table-condensed table-bordered' style='width:600px;'>""" for r in PrjList: txt += "<tr><td><a class='btn btn-primary' href='?src={0}'>Select {0}</a></td><td>{1}</td><td>{2}</td></tr>".format( *r) txt += """</table>""" return PrintInCharte(txt) if gvg("dest") == "": # il faut choisir le projet destination if current_user.has_role(database.AdministratorLabel): PrjList = GetAll( "select projid,title,status from projects order by lower(title)", cursor_factory=None) else: PrjList = GetAll("""select p.projid,title,status from projects p join projectspriv pp on p.projid = pp.projid where member=%s and privilege='Manage' order by lower(title)""", [current_user.id], cursor_factory=None) txt += "<h3>Select project destination project</h3> or <a class='btn btn-primary' href='?src={0}&dest=new'>New project</a>".format( gvg("src")) txt += """<table class='table table-condensed table-bordered' style='width:600px;'>""" for r in PrjList: txt += "<tr><td><a class='btn btn-primary' href='?src={0}&dest={1}'>Select {1}</a></td><td>{2}</td><td>{3}</td></tr>".format( gvg("src"), *r) txt += """</table>""" return PrintInCharte(txt) if gvg( "prjok" ) == "": # Creation du projet destination ou MAJ des attributs & detection des Taxo&Users Founds SrcPrj = GetAll( "select * from {0}.projects where projid={1}".format( newschema, gvg("src")), cursor_factory=RealDictCursor) if gvg("dest") == "new": # Creation du projet destination Prj = database.Projects() Prj.title = "IMPORT " + SrcPrj[0]['title'] db.session.add(Prj) db.session.commit() PrjPriv = database.ProjectsPriv() PrjPriv.projid = Prj.projid PrjPriv.member = current_user.id PrjPriv.privilege = "Manage" db.session.add(PrjPriv) else: # MAJ du projet Prj = database.Projects.query.filter_by( projid=int(gvg("dest"))).first() NbrObj = GetAll( "select count(*) from obj_head WHERE projid=" + gvg("dest"))[0][0] if NbrObj > 0: flash("Destination project must be empty", 'error') return PrintInCharte( "<a href='#' onclick='history.back();'>Back</a>") for k, v in SrcPrj[0].items(): if k not in ("projid", "title"): setattr(Prj, k, v) db.session.commit() flash( "Project %s:%s created or updated successfuly" % (Prj.projid, Prj.title), 'success') # Controle du mapping Taxo sql = """select DISTINCT t.id,lower(t.name) as name,t.parent_id,lower(t.name)||' ('||coalesce(lower(t2.name),'No Parent')||')' as namefull from {0}.obj_head o join {0}.taxonomy t on o.classif_id=t.id left join {0}.taxonomy t2 on t.parent_id=t2.id where o.projid={1} and t.newid is null union select DISTINCT t.id,lower(t.name) as name,t.parent_id,lower(t.name)||' ('||coalesce(lower(t2.name),'No Parent')||')' as namefull from {0}.obj_head o join {0}.taxonomy t on o.classif_auto_id=t.id left join {0}.taxonomy t2 on t.parent_id=t2.id left join taxonomy tt on t.newid =tt.id where o.projid={1} and tt.id is null order by 2""".format(newschema, gvg("src")) self.param.TaxoFound = GetAssoc(sql, cursor_factory=RealDictCursor, keyid='id', debug=False) app.logger.info("TaxoFound=%s", self.param.TaxoFound) TaxoInDest = GetAssoc2Col( """select t.id,lower(t.name)||' ('||coalesce(lower(t2.name),'No Parent')||')' as name from taxonomy t left join taxonomy t2 on t.parent_id=t2.id where t.id = any (%s)""", (list(self.param.TaxoFound.keys()), )) # print(TaxoInDest) for id, v in self.param.TaxoFound.items(): self.param.TaxoFound[id][ 'newid'] = None # par defaut pas de correspondance if id in TaxoInDest: if TaxoInDest[id].lower() == v['namefull']: self.param.TaxoFound[id][ 'newid'] = id # ID inchangé lst = [ t["name"] for t in self.param.TaxoFound.values() if t["newid"] is None ] # liste des Taxon sans mapping TaxoInDest = GetAssoc2Col( """select lower(t.name)||' ('||coalesce(lower(t2.name),'No Parent')||')' as name,t.id from taxonomy t left join taxonomy t2 on t.parent_id=t2.id where lower(t.name) = any (%s)""", (lst, )) for id, v in self.param.TaxoFound.items(): if v['newid'] is None: if v["namefull"] in TaxoInDest: self.param.TaxoFound[id]['newid'] = TaxoInDest[v[ "namefull"]] # ID du même nom dans la base de destination NotFoundTaxo = [ t["name"] for t in self.param.TaxoFound.values() if t["newid"] is None ] # liste des Taxon sans mapping restant app.logger.info("NotFoundTaxo=%s", NotFoundTaxo) # Controle du mapping utilisateur sql = """select DISTINCT lower(t.name) as name,lower(email) as email,t.password,t.organisation from {0}.obj_head o join {0}.users t on o.classif_who=t.id where o.projid={1} and t.newid is null union select DISTINCT lower(t.name) as name,lower(email) as email,t.password,t.organisation from {0}.projectspriv pp join {0}.users t on pp.member=t.id where pp.projid={1} and t.newid is null """.format(newschema, gvg("src")) self.param.UserFound = GetAssoc(sql, cursor_factory=RealDictCursor, keyid='name') self.pgcur = db.engine.raw_connection().cursor() self.pgcur.execute( "select id,lower(name),lower(email) from users where lower(name) = any(%s) or email= any(%s) ", (list(self.param.UserFound.keys()), [x.get('email') for x in self.param.UserFound.values()])) # Résolution des noms à partir du nom ou de l'email for rec in self.pgcur: for u in self.param.UserFound: if u == rec[1] or self.param.UserFound[u].get( 'email') == rec[2]: self.param.UserFound[u]['id'] = rec[0] logging.info("Users Found = %s", self.param.UserFound) NotFoundUser = [ k for k, v in self.param.UserFound.items() if v.get("id") == None ] if len(NotFoundUser) > 0: logging.info("Some Users Not Found = %s", NotFoundUser) self.UpdateParam() # On met à jour la Taxo if len(NotFoundUser) > 0 or len(NotFoundTaxo) > 0: txt += "<a class='btn btn-primary' href='?src={0}&dest={1}&prjok=1'>Continue</a>".format( gvg("src"), Prj.projid) else: txt += "<a class='btn btn-primary' href='?src={0}&dest={1}&prjok=2'>Continue</a>".format( gvg("src"), Prj.projid) return PrintInCharte(txt) if gvg("prjok") == "1" or gvg( "prjok" ) == "2": # 2 pas de mapping requis, simule un start task NotFoundTaxo = [ t["name"] for t in self.param.TaxoFound.values() if t["newid"] is None ] # liste des Taxon sans mapping restant NotFoundUsers = [ k for k, v in self.param.UserFound.items() if v.get("id") == None ] app.logger.info("TaxoFound=%s", self.param.TaxoFound) app.logger.info("NotFoundTaxo=%s", NotFoundTaxo) app.logger.info("NotFoundUser=%s", NotFoundUsers) if gvp('starttask') == "Y" or gvg("prjok") == "2": app.logger.info("Form Data = %s", request.form) # Traitement des reponses sur la taxonomy for i in range(1, 1 + len(NotFoundTaxo)): orig = gvp( "orig%d" % (i) ) #Le nom original est dans origXX et la nouvelle valeur dans taxolbXX origname = gvp( "origname%d" % (i) ) #Le nom original est dans origXX et la nouvelle valeur dans taxolbXX action = gvp("action%d" % (i)) newvalue = gvp("taxolb%d" % (i)) if origname in NotFoundTaxo and action != "": if action == "M": if newvalue == "": errors.append( "Taxonomy Manual Mapping : No Value Selected for '%s'" % (orig, )) else: t = database.Taxonomy.query.filter( database.Taxonomy.id == int( newvalue)).first() app.logger.info(orig + " associated to " + t.name) self.param.TaxoFound[orig]['newid'] = t.id elif action == "U": #create Under if newvalue == "": errors.append( "Taxonomy Manual Mapping : No Parent Value Selected for '%s'" % (orig, )) else: t = database.Taxonomy() t.name = origname t.parent_id = int(newvalue) db.session.add(t) db.session.commit() self.param.TaxoFound[orig]['newid'] = t.id app.logger.info(orig + " created under " + t.name) else: errors.append( "Taxonomy Manual Mapping : No Action Selected for '%s'" % (orig, )) else: errors.append( "Taxonomy Manual Mapping : Invalid value '%s' for '%s'" % (newvalue, orig)) # Traitement des reponses sur les utilisateurs for i in range(1, 1 + len(NotFoundUsers)): orig = gvp( "origuser%d" % (i) ) #Le nom original est dans origuserXX et la nouvelle valeur dans userlbXX action = gvp("useraction%d" % (i)) newvalue = gvp("userlb%d" % (i)) if orig in NotFoundUsers and (newvalue != "" or action == "C"): if action == "C": u = database.users() u.email = self.param.UserFound[orig]["email"] u.name = self.param.UserFound[orig]["name"] u.password = self.param.UserFound[orig][ "password"] u.organisation = ntcv( self.param.UserFound[orig]["organisation"] ) #+" Imported on "+datetime.datetime.now().strftime("%Y-%m-%d %H:%M") u.active = True db.session.add(u) db.session.commit() else: u = database.users.query.filter( database.users.id == int( newvalue)).first() app.logger.info("User " + orig + " associated to " + u.name) self.param.UserFound[orig]['id'] = u.id else: errors.append( "User Manual Mapping : Invalid value '%s' for '%s'" % (newvalue, orig)) app.logger.info("Final Taxofound = %s", self.param.TaxoFound) self.param.ProjectId = int(gvg("dest")) self.param.ProjectSrcId = int(gvg("src")) self.param.IntraStep = 1 self.UpdateParam() # On met à jour ce qui à été accepté # Verifier la coherence des données if len(errors) == 0: return self.StartTask(self.param, step=2) for e in errors: flash(e, "error") NotFoundTaxo = [ v["name"] for k, v in self.param.TaxoFound.items() if v["newid"] == None ] NotFoundUsers = [ k for k, v in self.param.UserFound.items() if v.get('id') == None ] app.logger.info("Final NotFoundTaxo = %s", NotFoundTaxo) NotFoundTaxoForTemplate = [{ 'id': v["id"], 'name': v["name"], 'namefull': v["namefull"] } for k, v in self.param.TaxoFound.items() if v["newid"] == None] return render_template('task/importdb_question1.html', header=txt, taxo=NotFoundTaxoForTemplate, users=NotFoundUsers) return PrintInCharte(txt) if self.task.taskstep == 2: # ################## Question Post Import Effectif d'un projet # Propose de voir le projet, de cleanner, ou d'importer un autre projet return PrintInCharte(self.GetDoneExtraAction())
def SPStep1(self): logging.info("Input Param = %s" % (self.param.__dict__, )) # self.param.ProjectId="2" Prj = database.Projects.query.filter_by( projid=self.param.ProjectId).first() # self.param.IntraStep=0 if getattr(self.param, 'IntraStep', 0) == 0: self.param.IntraStep = 1 db.session.expunge(Prj) NewPrj = Prj Prj = copy.copy( NewPrj) # Si on fait une copy on arrive plus à insérer. make_transient(NewPrj) NewPrj.title = self.param.subsetprojecttitle NewPrj.projid = None NewPrj.visible = False db.session.add(NewPrj) db.session.commit() pp = database.ProjectsPriv() pp.member = self.task.owner_id pp.privilege = "Manage" NewPrj.projmembers.append(pp) db.session.commit() self.param.subsetproject = NewPrj.projid self.UpdateProgress( 5, "Subset Project %d Created : %s" % (NewPrj.projid, NewPrj.title)) if self.param.IntraStep == 1: vaultroot = Path("../../vault") sqlparam = {'projid': self.param.ProjectId} sqlwhere = "" if self.param.extraprojects: sqlparam['projid'] += "," + self.param.extraprojects sqlparam['ranklimit'] = self.param.valeur if self.param.valtype == 'V': rankfunction = 'rank' elif self.param.valtype == 'P': rankfunction = '100*percent_rank' else: rankfunction = 'FunctionError' # if self.param.samplelist: # sqlwhere+=" and s.orig_id in (%s) "%(",".join(["'%s'"%x for x in self.param.samplelist.split(",")])) # sqlwhere+=" and (o.classif_qual in (%s) "%(",".join(["'%s'"%x for x in self.param.what.split(",")])) # if self.param.what.find('N')>=0: # sqlwhere+=" or o.classif_qual is null " # sqlwhere+=")" sqlwhere += sharedfilter.GetSQLFilter(self.param.filtres, sqlparam, str(self.task.owner_id)) logging.info("SQLParam=%s", sqlparam) sql = """select objid from ( SELECT """ + rankfunction + """() OVER (partition by classif_id order by random() )rang,o.objid from objects o left join samples s on o.sampleid=s.sampleid where o.projid in ( %(projid)s ) """ + sqlwhere + """ ) sr where rang<=%(ranklimit)s """ logging.info("SQL=%s %s", sql, sqlparam) # for obj in db.session.query(database.Objects).from_statement( text(sql) ).all(): LstObjects = GetAll(sql, sqlparam) logging.info("matched %s objects", len(LstObjects)) if len(LstObjects) == 0: self.task.taskstate = "Error" self.UpdateProgress( 10, "No object to include in the subset project") NbrObjects = 0 for objid in LstObjects: obj = db.session.query( database.Objects).filter_by(objid=objid[0]).first() objf = db.session.query( database.ObjectsFields).filter_by(objfid=objid[0]).first() objcnn = db.session.query( database.Objects_cnn_features).filter_by( objcnnid=objid[0]).first() NbrObjects += 1 oldobjid = obj.objid if self.param.withimg == 'Y': for img in obj.images: db.session.expunge(img) make_transient(img) self.pgcur.execute("select nextval('seq_images')") img.imgid = self.pgcur.fetchone()[0] # print("New Image id=",img.imgid) SrcImg = img.file_name SrcImgMini = img.thumb_file_name VaultFolder = "%04d" % (img.imgid // 10000) #creation du repertoire contenant les images si necessaire CreateDirConcurrentlyIfNeeded( vaultroot.joinpath(VaultFolder)) img.file_name = "%s/%04d%s" % (VaultFolder, img.imgid % 10000, Path(SrcImg).suffix) shutil.copyfile( vaultroot.joinpath(SrcImg).as_posix(), vaultroot.joinpath(img.file_name).as_posix()) if SrcImgMini is not None: img.thumb_file_name = "%s/%04d_mini%s" % ( VaultFolder, img.imgid % 10000, Path(SrcImgMini).suffix) shutil.copyfile( vaultroot.joinpath(SrcImgMini).as_posix(), vaultroot.joinpath( img.thumb_file_name).as_posix()) db.session.expunge(obj) make_transient(obj) obj.objid = None obj.img0id = None obj.projid = self.param.subsetproject obj.sampleid = self.GetSampleID(obj.sampleid) obj.processid = self.GetProcessID(obj.processid) obj.acquisid = self.GetAcquisID(obj.acquisid) db.session.add(obj) db.session.commit() dummy = objf.n01 #permet de forcer l'etat de objf sinon perd ses données sur les instruction suivantes. db.session.expunge(objf) make_transient(objf) objf.objfid = obj.objid db.session.add(objf) if objcnn: dummy = objcnn.cnn01 #permet de forcer l'etat de objcnn sinon perd ses données sur les instruction suivantes. db.session.expunge(objcnn) make_transient(objcnn) objcnn.objcnnid = obj.objid db.session.add(objcnn) db.session.commit() if NbrObjects % 20 == 0: self.UpdateProgress(5 + 95 * NbrObjects / len(LstObjects), "Subset creation in progress") # print (oldobjid,obj.objid) # Recalcule les valeurs de Img0 self.pgcur.execute("""update obj_head o set imgcount=(select count(*) from images where objid=o.objid) ,img0id=(select imgid from images where objid=o.objid order by imgrank asc limit 1 ) where projid=""" + str(self.param.subsetproject)) self.pgcur.connection.commit() import appli.project.main appli.project.main.RecalcProjectTaxoStat(self.param.subsetproject) appli.project.main.UpdateProjectStat(self.param.subsetproject) self.task.taskstate = "Done" self.UpdateProgress(100, "Subset created successfully")
def part_prjedit(pprojid): g.headcenter = "<h3>Particle Project Metadata edition</h3>" if pprojid > 0: model = partdatabase.part_projects.query.filter_by( pprojid=pprojid).first() if model.ownerid != current_user.id and not current_user.has_role( database.AdministratorLabel): return PrintInCharte(ErrorFormat("Access Denied")) else: if not (current_user.has_role(database.AdministratorLabel) or current_user.has_role(database.ProjectCreatorLabel)): return PrintInCharte(ErrorFormat("Access Denied")) model = partdatabase.part_projects() model.pprojid = 0 model.ownerid = current_user.id model.default_depthoffset = 1.2 model.public_visibility_deferral_month = app.config.get( 'PART_DEFAULT_VISIBLE_DELAY', '') model.public_partexport_deferral_month = app.config.get( 'PART_DEFAULT_GENERAL_EXPORT_DELAY', '') model.public_zooexport_deferral_month = app.config.get( 'PART_DEFAULT_PLANKTON_EXPORT_DELAY', '') UvpPrjForm.ownerid = SelectField( 'Project Owner', choices=database.GetAll( "SELECT id,name FROM users ORDER BY trim(lower(name))"), coerce=int) UvpPrjForm.projid = SelectField( 'Ecotaxa Project', choices=[(0, ''), (-1, 'Create a new EcoTaxa project')] + database.GetAll( "SELECT projid,concat(title,' (',cast(projid as varchar),')') FROM projects ORDER BY lower(title)" ), coerce=int) form = UvpPrjForm(request.form, model) if gvp('delete') == 'Y': try: db.session.delete(model) db.session.commit() return redirect("/part/prj/") except: flash( "You can delete a project only if doesn't have any data (sample,...)", 'error') db.session.rollback() return redirect("/part/prj/" + str(model.pprojid)) if request.method == 'POST' and form.validate(): if pprojid == 0: model.pprojid = None db.session.add(model) for k, v in form.data.items(): setattr(model, k, v) if model.projid == 0: # 0 permet de dire aucun projet WTForm ne sait pas gére None avec coerce=int model.projid = None if model.projid == -1: # création d'un projet Ecotaxa model.projid = None EcotaxaProject = database.Projects() EcotaxaProject.title = model.ptitle # Nommé comme le projet Particle db.session.add(EcotaxaProject) db.session.commit() EcotaxaProjectMember = database.ProjectsPriv() EcotaxaProjectMember.projid = EcotaxaProject.projid # L'utilisateur courant est Manager de ce projet EcotaxaProjectMember.member = current_user.id EcotaxaProjectMember.privilege = 'Manage' db.session.add(EcotaxaProjectMember) model.projid = EcotaxaProject.projid # On affecte le nouveau projet au projet Particle. db.session.commit() return redirect("/part/prj/" + str(model.pprojid)) return PrintInCharte( render_template("part/prjedit.html", form=form, prjid=model.pprojid))