def nut_report_update(message, args, sub_cmd, **kwargs): """ Client sent an update to an existing report Only the modified fields are sent. Each section is coded accoding to report codes. All fields are coded according to nutrsc. > nut report-update rgaudin -1355030878 0112 #P &SAM 1d:2 " 1h:2 1l6:2 #C &MAM a0:100 #T v:1 w:0 u:0-EOM-""" def resp_error(code, msg): # make sure we cancel whatever addition message.respond(u"nut report-update error %(code)s|%(msg)s" \ % {'code': code, 'msg': msg}) return True def provider_entity(provider): """ Entity a Provider is attached to """ try: return NUTEntity.objects.get(id=provider.first_access().target.id) except: return None # check that all parts made it together if not args.strip().endswith('-eom-'): return resp_error('BAD_FORM', REPORT_ERRORS['BAD_FORM']) else: args = args.strip()[:-5].strip() # split up sections sections = {} try: parts = args.strip().lower().split('#') for index in range(0, len(parts)): if index == 0: infos = parts[index] else: sections[parts[index][0].upper()] = parts[index][1:] pec_sec = sections.get('P', '').strip() cons_sec = sections.get('C', '').strip() order_sec = sections.get('O', '').strip() other_sec = sections.get('T', '').strip() except: return resp_error('BAD_FORM', REPORT_ERRORS['BAD_FORM']) # split up infos try: username, pwhash, date_str = infos.strip().split() except: return resp_error('BAD_FORM_INFO', REPORT_ERRORS['BAD_FORM_INFO']) # get Provider based on username try: provider = Provider.objects.get(user__username=username) except Provider.DoesNotExist: return resp_error('NO_ACC', REPORT_ERRORS['NO_ACC']) # check that provider pwhash is good if not provider.check_hash(pwhash): return resp_error('BAD_PASS', REPORT_ERRORS['BAD_PASS']) # check that user is not disabled if not provider.is_active: return resp_error('ACC_DIS', REPORT_ERRORS['ACC_DIS']) # check that user has permission to submit report on entity entity = provider_entity(provider) if not entity: return resp_error('NOT_ENT', REPORT_ERRORS['NOT_ENT']) eentity = Entity.objects.get(id=entity.id) if not provider_can('can_submit_report', provider, eentity): return resp_error('NO_PERM', REPORT_ERRORS['NO_PERM']) # parse date and check if period is valid try: month = int(date_str[0:2]) year = int('%s%s' % ('20', date_str[2:4])) period = MonthPeriod.find_create_from(year=year, month=month) except: return resp_error('BAD_FORM_PERIOD', REPORT_ERRORS['BAD_FORM_PERIOD']) # check period is the one we want if not period == current_reporting_period(): return resp_error('BAD_PERIOD', REPORT_ERRORS['BAD_PERIOD']) # global infos infos = {'entity': entity, 'eentity': eentity, 'provider': provider, 'month': month, 'year': year, 'period': period, 'username': username, 'pwhash': pwhash} # Retrieve report try: nut_report = NutritionReport.objects.get(period=infos['period'], entity=infos['entity'], type=Report.TYPE_SOURCE) except: return resp_error('MISS', REPORT_ERRORS['MISS']) reports = [] # common start of error message error_start = u"Impossible d'enregistrer le rapport. " logger.info("Processing PEC") if pec_sec: subs = pec_sec.split('&') subs = subs[1:] for sub in subs: fields = sub.split() cap = fields[0].lower() sub_report = getattr(nut_report, 'pec_%s_report' % cap) for field in fields[1:]: cfield, value = field.split(':') rfield = uncompress_pec_field(cfield) setattr(sub_report, rfield, int(value)) validator = PECReportValidator(sub_report) validator.errors.reset() try: validator.validate() except AttributeError as e: return resp_error('PEC_%s' % cap.upper(), error_start + e.__str__()) except: pass errors = validator.errors # return first error to user if errors.count() > 0: return resp_error('PEC_%s' % cap.upper(), error_start + errors.all()[0]) else: reports.append(sub_report) logger.info("Processing CONS") if cons_sec: subs = cons_sec.split('&') subs = subs[1:] for sub in subs: fields = sub.split() cap = fields[0].lower() logger.info(cap.upper()) for field in fields[1:]: cfield, value = field.split(':') rinpc, rfield = uncompress_cons_field(cfield) sub_report = getattr(getattr(nut_report, 'cons_%s_report' % cap), 'icr')(rinpc) setattr(sub_report, rfield, int(value)) if sub_report.valid and not sub_report in reports: reports.append(sub_report) logger.info("Processing ORDER") if order_sec: subs = order_sec.split('&') subs = subs[1:] for sub in subs: logger.info("\t%s" % sub) fields = sub.split() cap = fields[0].lower() for field in fields[1:]: cfield, value = field.split(':') rinpc, rfield = uncompress_cons_field(cfield) sub_report = getattr(getattr(nut_report, 'order_%s_report' % cap), 'icr')(rinpc) setattr(sub_report, rfield, int(value)) if not sub_report in reports: reports.append(sub_report) logger.info("Processing OTHER") if other_sec: fields = other_sec.split() for field in fields[1:]: cfield, value = field.split(':') rfield = uncompress_pec_field(cfield) sub_report = nut_report.pec_other_report setattr(sub_report, rfield, int(value)) # check validity relative to PEC if not sub_report.total == sub_report.nut_report.sum_all_other: return resp_error('OTHER_INT', REPORT_ERRORS['OTHER_INT']) else: reports.append(sub_report) # check validity of changes # save to DB @reversion.create_revision() @transaction.commit_manually def save_reports(reports, nut_report, provider=None): reversion.set_user(provider.user) reversion.set_comment("SMS report update") for report in reports: print("saving %s" % report) try: sub_report.save() except: transaction.rollback() return False try: nut_report._status = nut_report.STATUS_MODIFIED_AUTHOR nut_report.modified_by = provider nut_report.save() except: transaction.rollback() return False transaction.commit() return True logger.info("Saving reports") if not save_reports(reports, nut_report, provider): logger.warning("Unable to save reports") return resp_error('SRV', REPORT_ERRORS['SRV']) logger.info("Reports saved") ## CONFIRM RESPONSE confirm = "nut report-update ok %s" % nut_report.receipt message.respond(confirm) return True
def nut_report(message, args, sub_cmd, **kwargs): """ Client sent report for PEC CONS & ORDER Once the PEC MAM/SAM/SAM+ & CONS & ORDER is filled, all the data is sent via one single multipart SMS. SMS is divided in sections and sub sections #P #C #O respectively PEC, CONS and ORDER We don't combine sections in case we'll have to split message in different SMS. SMS can be 1000chars+ Sub sections for capabilities: &MAM, &SAM &SAMP Sub sub sections for age break downs |u6 |u59 |o59 |pw |fu1 |fu12 > nut report rgaudin 89080392890 1111 #P&MAM|u59 6817 7162 2164 1033 6527 5715 6749 2174 4201 3675 8412 2331 4868 4765 1896 2107 3457 6308 6238 6589 2432 5983 3871|pw 6817 7162 2164 1033 6527 5715 6749 2174 4201 3675 8412 2331 4868 4765 1896 2107 3457 6308 6238 6589 2432 5983 3871|fu12 6817 7162 2164 1033 6527 5715 6749 2174 4201 3675 8412 2331 4868 4765 1896 2107 3457 6308 6238 6589 2432 5983 3871&SAM|u59 6817 7162 2164 1033 6527 5715 6749 2174 4201 3675 8412 2331 4868 4765 1896 2107 3457 6308 6238 6589 2432 5983 3871|o59 6817 7162 2164 1033 6527 5715 6749 2174 4201 3675 8412 2331 4868 4765 1896 2107 3457 6308 6238 6589 2432 5983 3871|fu1 6817 7162 2164 1033 6527 5715 6749 2174 4201 3675 8412 2331 4868 4765 1896 2107 3457 6308 6238 6589 2432 5983 3871#C&MAM|csb 1199 1199 1199 1199|unimix 1199 1199 1199 1199|oil 1199 1199 1199 1199|sugar 1199 1199 1199 1199|mil 1199 1199 1199 1199|niebe 1199 1199 1199 1199&SAM|plumpy 1199 1199 1199 1199#O&MAM|csb 1199|unimix 1199|oil 1199|sugar 1199|mil 1199|niebe 1199&SAM|plumpy 1199#T 1 2 3-EOM- T lwb tb hiv < nut report error Error-code | Error Message < nut report ok #P$MAM GA1/gao-343-VO5$SAM GA1/gao-343-VO5#C$MAM GA1/gao-343-VO5$SAM GA1/gao-343-VO5#O$MAM GA1/gao-343-VO5$SAM GA1/gao-343-VO5 """ def resp_error(code, msg): # make sure we cancel whatever addition message.respond(u"nut report error %(code)s|%(msg)s" \ % {'code': code, 'msg': msg}) return True def provider_entity(provider): """ Entity a Provider is attached to """ try: return NUTEntity.objects.get(id=provider.first_access().target.id) except: return None def sub_sections_from_section(section): """ Returns an organised hash from raw string {'mam': {'u6': 'xx xx xx', 'o59': 'xx xx xx'}, 'sam': {}} """ subs = section.split('&') subs = subs[1:] subsh = {} for sub in subs: sub_data = sub.split('|') subh = {} for age_line in sub_data[1:]: age_ls = age_line.split() subh[age_ls[0]] = ' '.join(age_ls[1:]) subsh[sub_data[0]] = subh return subsh def check_capabilities(section, entity): """ return True if section's subs matches entity cap """ for cap in ('mam', 'sam', 'samp'): if getattr(entity, 'is_%s' % cap): if not cap in section.keys(): return False else: if cap in section.keys(): return False return True @reversion.create_revision() @transaction.commit_manually def save_reports(reports, user=None): reversion.set_user(provider.user) reversion.set_comment("SMS transmitted report") # save main first reports['main'].save() for secid, section in reports.items(): if secid == 'main': continue for catid, reports in section.items(): for report in reports: logger.info("%s > %s > %s" \ % (secid, catid, report.__class__)) try: # HACK: write foreign key id if needed if hasattr(report, 'nut_report_id'): report.nut_report_id = report.nut_report.id if hasattr(report, 'cons_report_id'): report.cons_report_id = report.cons_report.id if hasattr(report, 'order_report_id'): report.order_report_id = report.order_report.id report.save() except Exception as e: logger.error(u"Unable to save report to DB. " \ u"Message: %s | Exp: %r" \ % (message.content, e)) transaction.rollback() return False transaction.commit() return True # check that all parts made it together if not args.strip().endswith('-eom-'): return resp_error('BAD_FORM', REPORT_ERRORS['BAD_FORM']) else: args = args.strip()[:-5].strip() # split up sections try: infos, pec_sec, cons_sec, order_sec, other_sec = \ args.strip().lower().split('#') pec_sec = pec_sec[1:] cons_sec = cons_sec[1:] order_sec = order_sec[1:] other_sec = other_sec[1:] except: return resp_error('BAD_FORM', REPORT_ERRORS['BAD_FORM']) # split up infos try: username, pwhash, date_str = infos.strip().split() except: return resp_error('BAD_FORM_INFO', REPORT_ERRORS['BAD_FORM_INFO']) # get Provider based on username try: provider = Provider.objects.get(user__username=username) except Provider.DoesNotExist: return resp_error('NO_ACC', REPORT_ERRORS['NO_ACC']) # check that provider pwhash is good if not provider.check_hash(pwhash): return resp_error('BAD_PASS', REPORT_ERRORS['BAD_PASS']) # check that user is not disabled if not provider.is_active: return resp_error('ACC_DIS', REPORT_ERRORS['ACC_DIS']) # check that user has permission to submit report on entity entity = provider_entity(provider) if not entity: return resp_error('NOT_ENT', REPORT_ERRORS['NOT_ENT']) eentity = Entity.objects.get(id=entity.id) if not provider_can('can_submit_report', provider, eentity): return resp_error('NO_PERM', REPORT_ERRORS['NO_PERM']) # parse date and check if period is valid try: month = int(date_str[0:2]) year = int('%s%s' % ('20', date_str[2:4])) period = MonthPeriod.find_create_from(year=year, month=month) except: return resp_error('BAD_FORM_PERIOD', REPORT_ERRORS['BAD_FORM_PERIOD']) # check period is the one we want if not period == current_reporting_period(): return resp_error('BAD_PERIOD', REPORT_ERRORS['BAD_PERIOD']) # report receipts holder for confirm message #report_receipts = {} # reports holder for delayed database commit reports = {} # global infos infos = {'entity': entity, 'eentity': eentity, 'provider': provider, 'month': month, 'year': year, 'period': period, 'username': username, 'pwhash': pwhash} # UNIQUENESS if NutritionReport.objects.filter(period=infos['period'], entity=infos['entity'], type=Report.TYPE_SOURCE).count() > 0: return resp_error('UNIQ', REPORT_ERRORS['UNIQ']) # create main report try: period = MonthPeriod.find_create_from(year=year, month=month) nut_report = NutritionReport.start(period, entity, provider, type=Report.TYPE_SOURCE) reports['main'] = nut_report except Exception as e: #raise logger.error(u"Unable to save report to DB. Message: %s | Exp: %r" \ % (message.content, e)) return resp_error('SRV', REPORT_ERRORS['SRV']) # SECTIONS for sid, section in {'P': 'pec', 'C': 'cons', 'O': 'order', 'T': 'other'}.items(): logger.info("Processing %s" % section) # extract/split sub sections info from string if sid == 'T': sec = eval('%s_sec' % section) else: sec = sub_sections_from_section(eval('%s_sec' % section)) # check that capabilities correspond to entity if sid != 'T' and not check_capabilities(sec, entity): return resp_error('BAD_CAP', REPORT_ERRORS['BAD_CAP']) # call sub-report section handler sec_succ, sec_data = SUB_REPORTS.get(section)(message, sec, infos, reports, nut_report=nut_report) # cancel if sub report failed. if not sec_succ: logger.warning(u" FAILED.") return resp_error(sec_data[0], sec_data[1]) # add sub-report to list of reports reports[sid] = sec_data logger.info("---- Ended %s" % section) ## DB COMMIT # create the reports in DB # save receipts number logger.info("Saving reports") if not save_reports(reports, user=provider.user): logger.warning("Unable to save reports") return resp_error('SRV', REPORT_ERRORS['SRV']) logger.info("Reports saved") def flatten(iterable): values = [] def add(value): if not isinstance(value, (list, dict)): values.append(value) return True return False if not add(iterable): miterable = iterable.values() if isinstance(iterable, dict) \ else iterable for item in miterable: values += flatten(item) return values # for report in flatten(reports): # with reversion.create_revision(): # reversion.set_user(provider.user) # reversion.set_comment("SMS transmitted report") # report.save() ## CONFIRM RESPONSE confirm = "nut report ok %s" % nut_report.receipt message.respond(confirm) return True ## TRIGGER ALERT """