def set_errors(form, builder, fields): log.error("Form is not valid") log.debug(form.errors.as_json()) for item in form.errors: if "_id" in item: item = item.split("_id")[0] # Get the widget if ("entry" in fields and item in fields["entry"]) or \ ("spinbutton" in fields and item in fields["spinbutton"]): entry = builder.get_object(item) entry.get_style_context().add_class("error") entry.set_icon_from_icon_name(1, "error") item_errors = "\n".join(form.errors[item]) entry.set_icon_tooltip_markup(1, item_errors) entry.connect("changed", error_field_changed) if "combobox" in fields and item in fields["combobox"]: combobox = builder.get_object(item) combobox.get_style_context().add_class("error-combobox") combobox.connect("changed", error_field_changed) if "textview" in fields and item in fields["textview"]: textview = builder.get_object(item + "_buffer") textview.get_style_context().add_class("error-textview") textview.connect("changed", error_field_changed) return
def on_language(self, action, param): log.info("Lang changed: %s", param) action.set_state(param) self.params.application.lang = param.get_string() self.params.save() set_langage(self.params.application.lang) # Get window parameters window_size = self.window.get_size() # Position is not reliable on Wayland window_position = self.window.get_position() log.debug(window_position) mode = self.window.mode self.window.destroy() self.window = AppWindow(application=self, params=self.params, actions=self) self.window.set_size_request(800, 600) self.set_mode(mode) self.window.set_default_size(*window_size) self.window.move(x=window_position[0], y=window_position[1]) self.window.show_all()
def add_key(self, import_file): """Add a key to the keyring. :param file-object import_file: File object containing the key data. :return: True if the key is successfully added. :rtype: bool :Example: >>> from pathlib import Path >>> my_key_file = Path("pharmaship.pub").open() >>> km = KeyManager() >>> km.add_key(my_key_file) True """ key_data = import_file.read() r = self.gpg.import_keys(key_data) for item in r.results: if 'ok' not in item.keys(): log.error("Signature error: %s", item["text"]) self.status = _('Something went wrong! See detailed logs.') return False if r.imported == 0: log.info("Key already added.") self.status = _('Key already added') else: key = self.get_key(r.fingerprints[0]) item = {"name": key['uids'][0], "fingerprint": key['fingerprint']} log.debug(item) self.status = _('Key successfully added') return True
def test_create_molecules(self): required = rescue_bag.get_required(self.params) output = rescue_bag.create_molecules(required["molecules"].keys(), required["molecules"]) image_field = TypeDefinition(name='image_field', included_types=(ImageFieldFile, ), excluded_types=()) Validator.types_mapping['image_field'] = image_field schema_path = settings.VALIDATOR_PATH.joinpath("parsers", "rescue_bag", "single_item.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) for item in output: result = validator.validate(output[item]) if not result: log.error(validator.errors) log.debug(output[item]) self.assertTrue(result) schema = {"data": {"type": "dict", "keysrules": {"type": "integer"}}} validator = Validator(schema) self.assertTrue(validator.validate({"data": output}))
def test_telemedical_parser(self): """Check conformity of parser's output. Proceed in two steps: 1. Check the conformity of output items 2. Check the conformity of output keys Use Cerberus for checking conformity. """ output = parsers.laboratory.parser(self.params) schema_path = settings.VALIDATOR_PATH / "parsers" / "telemedical.json" schema = json.loads(schema_path.read_text()) group_type = TypeDefinition( name='equipment_group', included_types=(models.EquipmentGroup,), excluded_types=() ) image_field = TypeDefinition( name='image_field', included_types=(ImageFieldFile,), excluded_types=() ) Validator.types_mapping['equipment_group'] = group_type Validator.types_mapping['image_field'] = image_field validator = Validator(schema) dict_result = {"data": output} result = validator.validate(dict_result) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def reset(self, source): log.debug("Reload vessel data and re-build the view.") for child in self.scrolled.get_children(): child.destroy() self.build_form() self.builder.get_object("actionbar").hide()
def test_merge_bags(self): required = rescue_bag.get_required(self.params) equipments = rescue_bag.get_articles(self.params, required["equipments"], [110, 111]) molecules = rescue_bag.get_medicines(self.params, required["molecules"], [110, 111]) bags = models.RescueBag.objects.all() output = rescue_bag.merge_bags(bags, molecules, equipments) image_field = TypeDefinition(name='image_field', included_types=(ImageFieldFile, ), excluded_types=()) Validator.types_mapping['image_field'] = image_field schema_path = settings.VALIDATOR_PATH.joinpath("parsers", "rescue_bag", "merged_bags.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) result = validator.validate({"data": output}) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def test_get_transactions(self): schema_path = settings.VALIDATOR_PATH.joinpath( "parsers", "rescue_bag", "get_transactions.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) # Test for medicines content_type = self.params.content_types["medicine"] items = models.Medicine.objects.filter(used=False).values_list( "id", flat=True) output = rescue_bag.get_transactions(content_type, items) result = validator.validate({"data": output}) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result) # Test for articles content_type = self.params.content_types["article"] items = models.Article.objects.filter(used=False).values_list( "id", flat=True) output = rescue_bag.get_transactions(content_type, items) result = validator.validate({"data": output}) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def get_base(type, content, model=None): """Return a model instance according to the type and model if provided. If the `model` is not provided, it is retrieved from the `type` structure. On some `model` (ie: :class:`pharmaship.inventory.models.RescueBagReqQty`), the type can be either :class:`pharmaship.inventory.models.Equipment` or :class:`pharmaship.inventory.models.Molecule`. :param type: Model class of base field (Django internal). :type type: models.Equipment or models.Molecule :param dict content: Dictionnary with all natural key fields for the\ related base. :param model: Model class of base item to serialize. :type model: models.Equipment or models.Molecule :return: A model instance or ``None`` if not found. :rtype: models.Equipment or models.Molecule or None """ if not model: try: model = type.field.related_model except AttributeError as error: log.error("Model class not found: %s", error) return None try: instance = model.objects.get_by_natural_key(**content) except model.DoesNotExist: log.error("%s instance does not exist.", model) log.debug(content) return None return instance
def check_integrity(tar_file): """Check the files listed in the ``MANIFEST`` and their checksum. :param tarfile.Tarfile tar_file: The tar file to check :return: ``True`` if the content is conform to the MANIFEST. :rtype: bool """ # Open the MANIFEST names = tar_file.getnames() if "MANIFEST" not in names: log.error("MANIFEST not found.") return False manifest = extract_manifest(tar_file.extractfile("MANIFEST")) # TODO: Raise an error when a file (not a directory) in the archive # is not in the MANIFEST. # Check the SHA256sum for item in manifest: if item['filename'] not in names: log.debug(names) log.error("File not in the tar file: %s", item['filename']) return False m = hashlib.sha256() m.update(tar_file.extractfile(item['filename']).read()) tarfile_hash = m.hexdigest() if tarfile_hash != item['hash']: log.error("File corrupted: %s", item['filename']) return False return True
def dialog_add(self, source, equipment): builder = Gtk.Builder.new_from_file( utils.get_template("article_add.glade")) dialog = builder.get_object("dialog") label = builder.get_object("equipment") label.set_text("{0} ({1})".format(equipment["name"], equipment["packaging"])) # Check if equipment has previous locations to input the latest one as # default to ease the input active_location = None equipment_obj = models.Equipment.objects.get(id=equipment["id"]) try: latest_article = equipment_obj.articles.latest("exp_date") except models.Article.DoesNotExist: latest_article = None # except ObjectDoesNotExist: # latest_article = None if latest_article: active_location = latest_article.location.id log.debug("Found last location: %s", active_location) location_combo = builder.get_object("location") utils.location_combo(combo=location_combo, locations=self.params.locations, active=active_location) # By default name = equipment name name = builder.get_object("name") name.set_text(equipment["name"]) # Expiry date input mask workaround exp_date = builder.get_object("exp_date_raw") exp_date = utils.grid_replace(exp_date, widgets.EntryMasked(mask=DATE_MASK)) # exp_date.connect("activate", self.response_add, dialog, equipment, builder) builder.expose_object("exp_date", exp_date) # Connect signals # builder.connect_signals({ # "on_entry_activate": (self.response_add, dialog, equipment, builder) # }) builder.connect_signals({ "on-response": (self.response_add, dialog, equipment, builder), "on-cancel": (utils.dialog_destroy, dialog) }) query_count_all() dialog.set_transient_for(self.window) dialog.run() dialog.destroy()
def allowance(self, args): """Export selected `Allowance` instance in a tar file.""" log.debug("Allowance export") try: allowance = models.Allowance.objects.get(id=args["id"]) except models.Allowance.DoesNotExist: log.error("Allowance does not exists.") exit() create_archive(allowance, args["filename"])
def contents(self, element): """Return `element` contents list.""" if "medicines" in element: return element["medicines"] elif "articles" in element: return element["articles"] elif "contents" in element: return element["contents"] log.warning("Content not identified. Skipping. See debug log.") log.debug(element) return []
def deserialize_json_file(data, tar, allowance): """Deserialize a JSON file contained in the tar file. :param dict data: Dictionnary with filename and model related. The \ following keys must be present: * ``filename``: the name of the JSON file to extract from the tar \ archive; * ``model``: the class of model to deserialize \ (ie: :class:`pharmaship.inventory.models.MoleculeReqQty`). :param tarfile.TarFile tar: tar file archive containing the file to extract :param allowance: allowance instance to rattach :type allowance: models.Allowance :return: List of `model` instances. :rtype: list """ content = get_file(data["filename"], tar) if not content: return False try: item_data = json.loads(content) except json.decoder.JSONDecodeError as error: log.error("Corrupted JSON file: %s", error) return False objects = [] for item in item_data: if "content_type" not in item: base = get_base(type=data["model"].base, content=item["base"]) else: ct = get_model(item["content_type"]) if ct is None: return False base = get_base(type=data["model"].base, content=item["base"], model=ct.model_class()) if not base: log.error("Base for item not found.") log.debug(item) return False instance = data["model"](allowance=allowance, base=base, required_quantity=item["required_quantity"]) objects.append(instance) return objects
def test_get_required(self): output = rescue_bag.get_required(self.params) schema_path = settings.VALIDATOR_PATH.joinpath("parsers", "rescue_bag", "get_required.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) result = validator.validate(output) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def dialog_add(self, source, molecule): builder = Gtk.Builder.new_from_file( utils.get_template("medicine_add.glade")) dialog = builder.get_object("dialog") label = builder.get_object("molecule") label.set_text("{0} ({1} - {2})".format(molecule["name"], molecule["dosage_form"], molecule["composition"])) # Check if molecule has previous locations to input the latest one as # default to ease the input active_location = None molecule_obj = models.Molecule.objects.get(id=molecule["id"]) try: latest_medicine = molecule_obj.medicines.latest("exp_date") except models.Medicine.DoesNotExist: latest_medicine = None if latest_medicine: active_location = latest_medicine.location.id log.debug("Found last location: %s", active_location) location_combo = builder.get_object("location") utils.location_combo(combo=location_combo, locations=self.params.locations, active=active_location) # By default name = molecule name name = builder.get_object("name") name.set_text(molecule["name"]) # Expiry date input mask workaround exp_date = builder.get_object("exp_date_raw") exp_date = utils.grid_replace(exp_date, widgets.EntryMasked(mask=DATE_MASK)) # exp_date.connect("activate", self.response_add, dialog, molecule, builder) builder.expose_object("exp_date", exp_date) # Connect signals builder.connect_signals({ # "on-entry-activate": (self.response_add, dialog, molecule, builder), "on-response": (self.response_add, dialog, molecule, builder), "on-cancel": (utils.dialog_destroy, dialog) }) query_count_all() dialog.set_transient_for(self.window) dialog.run() dialog.destroy()
def test_search(self): call_command("loaddata", self.assets / "test.dump.yaml") text = "Doli" params = GlobalParameters() output = search.search(text, params) schema_path = settings.VALIDATOR_PATH.joinpath("search.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) result = validator.validate({"data": output}) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def import_molecule(self): """Import Molecule objects from a YAML file. Use Django's update_or_create method for :class:`pharmaship.inventory.models.Molecule`. :return: ``True`` if successful import, ``False`` otherwise. :rtype: bool """ content = get_file("inventory/molecule_obj.yaml", self.tar) if not content: return False try: deserialized_list = serializers.deserialize("yaml", content) deserialized_list = list(deserialized_list) except serializers.base.DeserializationError as error: log.error("Cannot deserialize molecule objects: %s", error) return False for molecule in deserialized_list: # Unique: (name, roa, dosage_form, composition) unique_values = { 'name': molecule.object.name, 'roa': molecule.object.roa, 'dosage_form': molecule.object.dosage_form, 'composition': molecule.object.composition, } molecule_dict = dict(unique_values) # Hard copy molecule_dict['medicine_list'] = molecule.object.medicine_list molecule_dict['group'] = molecule.object.group molecule_dict['remark'] = molecule.object.remark # TODO: DB calls optimization obj, created = models.Molecule.objects.update_or_create( defaults=molecule_dict, **unique_values) # Add M2M relations try: if molecule.m2m_data['tag']: obj.tag = molecule.m2m_data['tag'] obj.save() except KeyError: pass if created: log.debug("Created molecule: %s", obj) return True
def create_archive(allowance, file_obj): """Create an archive from the given `Allowance` instance. The response is a tar.gz file containing YAML files generated by the function `serialize_allowance`. """ # Creating a tar.gz archive hashes = [] serialized_data, equipment_list = serialize_allowance( allowance=allowance, content_types=get_content_types() ) with tarfile.open(fileobj=file_obj, mode='w') as tar: # Processing the database for item in serialized_data: info, f = create_tarinfo(item[0], item[1]) tar.addfile(info, f) hashes.append(get_hash(info.name, content=item[1])) # Adding the pictures of Equipment for item in get_pictures(equipment_list): picture_filename = settings.PICTURES_FOLDER / item log.debug(picture_filename) try: tar.add(picture_filename, arcname=PurePath("pictures", item)) # TODO: Detail Exception except Exception as error: log.error("Error: %s", error) hashes.append(get_hash(PurePath("pictures", item), filename=picture_filename)) # Add the MANIFEST package_content = create_package_yaml(allowance) info, f = create_tarinfo("package.yaml", package_content) tar.addfile(info, f) hashes.append(get_hash("package.yaml", content=package_content)) # Add the MANIFEST manifest_content = create_manifest(hashes) info, f = create_tarinfo("MANIFEST", manifest_content) tar.addfile(info, f) return True
def test_parser(self): output = rescue_bag.parser(self.params) image_field = TypeDefinition(name='image_field', included_types=(ImageFieldFile, ), excluded_types=()) Validator.types_mapping['image_field'] = image_field schema_path = settings.VALIDATOR_PATH.joinpath("parsers", "rescue_bag", "rescue_bag.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) result = validator.validate({"data": output}) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def create_rescue_bags(total): current = RescueBag.objects.count() missing = total - current if missing < 1: # Nothing to do log.debug("Number of RescueBag in database higher than needed") return True log.info("Missing some rescue bag instances. Creating...") for i in range(missing): location = Location.objects.create(name="Rescue Bag {0}".format(i), parent_id=0, is_rescue_bag=True) RescueBag.objects.create(name="Rescue Bag {0}".format(i), location_id=location.id) return True
def get_model(data): """Return the related ContentType from `data`. :param dict data: Dictionnary containing at least following keys: * ``app_label``: the application name, * ``name``: the name of the model :return: The Django ContentType instance or ``None`` if it does not exist. :rtype: django.contrib.contenttypes.models.ContentType or None """ try: ct = ContentType.objects.get_by_natural_key( app_label=data["app_label"], model=data["name"]) except ContentType.DoesNotExist: log.error("ContentType not found.") log.debug(data) return None return ct
def import_equipment(self): """Import Equipment objects from a YAML file. Use Django's update_or_create method for :class:`pharmaship.inventory.models.Equipment`. :return: ``True`` if successful import, ``False`` otherwise. :rtype: bool """ content = get_file("inventory/equipment_obj.yaml", self.tar) if not content: return False try: deserialized_list = serializers.deserialize("yaml", content) deserialized_list = list(deserialized_list) except serializers.base.DeserializationError as error: log.error("Cannot deserialize equipment objects: %s", error) return False for equipment in deserialized_list: # Unique: (name, packaging, perishable, consumable) unique_values = { 'name': equipment.object.name, 'packaging': equipment.object.packaging, 'perishable': equipment.object.perishable, 'consumable': equipment.object.consumable } equipment_dict = dict(unique_values) # Hard copy equipment_dict["group"] = equipment.object.group equipment_dict['picture'] = equipment.object.picture equipment_dict['remark'] = equipment.object.remark # TODO: DB calls optimization obj, created = models.Equipment.objects.update_or_create( defaults=equipment_dict, **unique_values) if created: log.debug("Created equipment: %s", obj) return True
def test_parser(self): kits = models.FirstAidKit.objects.all() output = first_aid.parser(self.params, kits) self.assertIsInstance(output, list) image_field = TypeDefinition(name='image_field', included_types=(ImageFieldFile, ), excluded_types=()) Validator.types_mapping['image_field'] = image_field schema_path = settings.VALIDATOR_PATH.joinpath("parsers", "first_aid", "first_aid_kit.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) for item in output: result = validator.validate(item) if not result: log.error(validator.errors) log.debug(item) self.assertTrue(result)
def test_parse_items(self): call_command( "loaddata", self.assets / "locations.yaml", self.assets / "search_parse_items.yaml", ) items = [] output = search.parse_items(items) schema = { "data": { "type": "list", "items": [{ "type": "list", "schema": { "type": "integer", "min": 0 } }, { "type": "dict", "keysrules": { "type": "integer", "min": 0 }, "valuesrules": { "type": "integer", "min": 0 } }] } } validator = Validator(schema) result = validator.validate({"data": output}) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def test_medicine_parser(self): """Check conformity of parser's output. Proceed in two steps: 1. Check the conformity of output items 2. Check the conformity of output keys Use Cerberus for checking conformity. """ output = parsers.medicines.parser(self.params) schema_path = settings.VALIDATOR_PATH / "parsers" / "medicines.json" schema = json.loads(schema_path.read_text()) group_type = TypeDefinition( name='molecule_group', included_types=(models.MoleculeGroup,), excluded_types=() ) Validator.types_mapping['molecule_group'] = group_type validator = Validator(schema) for item in output: dict_result = {"data": output[item]} result = validator.validate(dict_result) if not result: log.error(validator.errors) log.debug(output[item]) self.assertTrue(result) schema = { "data": { "type": "dict", "keysrules": { "type": "molecule_group" } } } validator = Validator(schema) self.assertTrue(validator.validate({"data": output}))
def test_create_equipment(self): required = rescue_bag.get_required(self.params) equipment = models.Equipment.objects.get(id=2) output = rescue_bag.create_equipment(equipment, required["equipments"]) image_field = TypeDefinition(name='image_field', included_types=(ImageFieldFile, ), excluded_types=()) Validator.types_mapping['image_field'] = image_field schema_path = settings.VALIDATOR_PATH.joinpath("parsers", "rescue_bag", "single_item.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) result = validator.validate(output) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def test_get_available_articles(self): element = models.Equipment.objects.get(pk=1) items_id_list = element.articles.all().values_list("id", flat=True) transactions = first_aid.get_transactions( content_type=self.params.content_types["article"], items=items_id_list) output = first_aid.get_available_articles( element=element, content_type_id=self.params.content_types["article"], qty_transactions=transactions) schema_path = settings.VALIDATOR_PATH.joinpath("parsers", "first_aid", "available_items.json") schema = json.loads(schema_path.read_text()) validator = Validator(schema) result = validator.validate({"data": output}) if not result: log.error(validator.errors) log.debug(output) self.assertTrue(result)
def dialog_delete(self, total): # Check the real number of RescueBag instances to delete current = RescueBag.objects.count() count = current - total if count <= 0: log.debug( "Number of RescueBag in database is high than requested.") return builder = Gtk.Builder.new_from_file( get_template("rescue_bag_dialog.glade")) dialog = builder.get_object("dialog") # Set message (showing remaining rescue bags to select for deletion) # Adapt for plural form label = builder.get_object("label") btn = builder.get_object("btn-delete") if count < 2: msg_text = _("Select the rescue bag to delete.") btn.set_label(_("Delete this rescue bag")) else: msg_text = _("Select the {0} rescue bags to delete.") btn.set_label(_("Delete these rescue bags")) label.set_text(msg_text.format(count)) # Create the Treeview self.build_rescue_bag_tree(builder, count) # Connect signals builder.connect_signals({ "on-response": (self.response_delete, dialog, builder, count), "on-cancel": (self.dialog_destroy, dialog), }) query_count_all() dialog.run() dialog.destroy()
def required_quantity(data, tar, allowance): """Update the required quantities for deserialized items. After successful deserialization, delete all related required quantity for the selected allowance. Then create all deserialized objects. :param dict data: Dictionnary with filename and model related. The \ following keys must be present: * ``filename``: the name of the JSON file to extract from the tar \ archive; * ``model``: the class of model to deserialize \ (ie: :class:`pharmaship.inventory.models.MoleculeReqQty`). :param tarfile.TarFile tar: tar file archive containing the file to extract :param allowance: allowance instance to rattach :type allowance: models.Allowance :return: ``True`` if there is no error, ``False`` otherwise. :rtype: bool """ log.debug("Updating required quantities for %s", data["filename"]) deserialized_list = deserialize_json_file(data, tar, allowance) if deserialized_list is False: log.error("Error when deserializing file: %s", data["filename"]) return False # As we are sure that the deserialization went fine, # delete all required quantities entry and create new ones data["model"].objects.filter(allowance_id__in=(0, allowance.id)).delete() data["model"].objects.bulk_create(deserialized_list) log.debug("Created %s instances", len(deserialized_list)) query_count_all() return True