def get_items_metadata(self): """Get the metadata of items to bulk update.""" def get_file_data(meta): file_data = {} for key in meta: if isinstance(meta.get(key), list): for item in meta.get(key): if isinstance(item, dict) and 'filename' in item: file_data[key] = meta.get(key) break return file_data pids = request.values.get('pids') pid_list = [] if pids is not None: pid_list = pids.split('/') data = {} for pid in pid_list: record = WekoRecord.get_record_by_pid(pid) indexes = [] if isinstance(record.get('path'), list): for path in record.get('path'): indexes.append(path.split('/')[-1]) pidObject = PersistentIdentifier.get('recid', pid) meta = ItemsMetadata.get_record(pidObject.object_uuid) if meta: data[pid] = {} data[pid]['meta'] = meta data[pid]['index'] = {"index": indexes} data[pid]['contents'] = get_file_data(meta) return jsonify(data)
def check_identifier_new(item): """Check data Identifier. :argument item -- {dict} item import. item_exist -- {dict} item in system. :return return -- Name of key if is Identifier. """ if item.get('Identifier key'): item_iden = item.get('metadata', '').get(item.get('Identifier key')) for it in item_iden: try: pids = [ k for k in it.values() if k != 'DOI' or k != 'HDL'] for pid in pids: item_check = \ WekoRecord.get_record_by_pid(pid) if item_check and item_check.id != item.id: item['errors'] = ['Errors in Identifier'] item['status'] = '' except BaseException: current_app.logger.error('Unexpected error: ', sys.exc_info()[0]) return item
def get_resource_dump_manifest(self, record_id): """ Get resource dump manifest. :param record_id: Identifier of record. :return: (xml) content of resourcedumpmanifest """ _validation = self._validation(record_id) if self.resource_dump_manifest and _validation: rdm = ResourceDumpManifest() rdm.up = '{}resync/{}/resourcedump.xml'.format( request.url_root, self.repository_id) record = WekoRecord.get_record_by_pid(record_id) if record: for file in record.files: current_app.logger.debug(file.info()) file_info = file.info() path = 'recid_{}/{}'.format(record.get('recid'), file_info.get('key')) lastmod = str(datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc).isoformat()) rdm.add( Resource( '{}record/{}/files/{}'.format( request.url_root, record.get('recid'), file_info.get('key')), lastmod=lastmod, sha256=file_info.get('checksum').split(':')[1], length=str(file_info.get('size')), path=path)) return rdm.as_xml() return None
def record_from_pid(pid_value): """Get record from PID.""" try: return WekoRecord.get_record_by_pid(pid_value) except Exception as e: current_app.logger.debug('Unable to get version record: ') current_app.logger.debug(e) return {}
def get_contribute_tree(cls, pid, root_node_id=0): """Get Contrbute tree.""" from weko_deposit.api import WekoRecord record = WekoRecord.get_record_by_pid(pid) tree = cls.get_index_tree(root_node_id) if record.get('_oai'): reset_tree(tree=tree, path=record.get('path')) else: reset_tree(tree=tree, path=[]) return tree
def iframe_items_index(pid_value=0): """Iframe items index.""" try: if pid_value == 0: return redirect(url_for('.iframe_index')) record = WekoRecord.get_record_by_pid(pid_value) action = 'private' if record.get('publish_status', '1') == '1' \ else 'publish' if request.method == 'GET': return render_template( 'weko_items_ui/iframe/item_index.html', render_widgets=True, pid_value=pid_value, action=action, activity=session['itemlogin_activity'], item=session['itemlogin_item'], steps=session['itemlogin_steps'], action_id=session['itemlogin_action_id'], cur_step=session['itemlogin_cur_step'], record=session['itemlogin_record'], histories=session['itemlogin_histories'], res_check=session['itemlogin_res_check'], pid=session['itemlogin_pid'], community_id=session['itemlogin_community_id']) if request.headers['Content-Type'] != 'application/json': flash(_('Invalid Request'), 'error') return render_template('weko_items_ui/iframe/item_index.html', render_widgets=True) data = request.get_json() sessionstore = RedisStore( redis.StrictRedis.from_url('redis://{host}:{port}/1'.format( host=os.getenv('INVENIO_REDIS_HOST', 'localhost'), port=os.getenv('INVENIO_REDIS_PORT', '6379')))) if request.method == 'PUT': """update index of item info.""" item_str = sessionstore.get('item_index_{}'.format(pid_value)) sessionstore.delete('item_index_{}'.format(pid_value)) current_app.logger.debug(item_str) item = json.loads(item_str) item['index'] = data current_app.logger.debug(item) elif request.method == 'POST': """update item data info.""" sessionstore.put('item_index_{}'.format(pid_value), json.dumps(data), ttl_secs=300) return jsonify(data) except BaseException: current_app.logger.error('Unexpected error: ', sys.exc_info()[0]) return abort(400)
def items_index(pid_value='0'): """Items index.""" try: if pid_value == '0' or pid_value == 0: return redirect(url_for('.index')) record = WekoRecord.get_record_by_pid(pid_value) action = 'private' if record.get('publish_status', '1') == '1' \ else 'publish' from weko_theme.utils import get_design_layout # Get the design for widget rendering page, render_widgets = get_design_layout( current_app.config['WEKO_THEME_DEFAULT_COMMUNITY']) if request.method == 'GET': return render_template( current_app.config['WEKO_ITEMS_UI_INDEX_TEMPLATE'], page=page, render_widgets=render_widgets, pid_value=pid_value, action=action) if request.headers['Content-Type'] != 'application/json': flash(_('Invalid request'), 'error') return render_template( current_app.config['WEKO_ITEMS_UI_INDEX_TEMPLATE'], page=page, render_widgets=render_widgets) data = request.get_json() sessionstore = RedisStore( redis.StrictRedis.from_url('redis://{host}:{port}/1'.format( host=os.getenv('INVENIO_REDIS_HOST', 'localhost'), port=os.getenv('INVENIO_REDIS_PORT', '6379')))) if request.method == 'PUT': """update index of item info.""" item_str = sessionstore.get('item_index_{}'.format(pid_value)) sessionstore.delete('item_index_{}'.format(pid_value)) current_app.logger.debug(item_str) item = json.loads(item_str) item['index'] = data current_app.logger.debug(item) elif request.method == 'POST': """update item data info.""" sessionstore.put('item_index_{}'.format(pid_value), json.dumps(data), ttl_secs=300) return jsonify(data) except BaseException: current_app.logger.error('Unexpected error: ', sys.exc_info()[0]) return abort(400)
def handle_check_exist_record(list_recond) -> list: """Check record is exist in system. :argument list_recond -- {list} list recond import. :return return -- list record has property status. """ result = [] for item in list_recond: if not item.get('errors'): item = dict(**item, **{ 'status': 'new' }) try: item_id = item.get('id') if item_id: item_exist = WekoRecord.get_record_by_pid(item_id) if item_exist: if item_exist.pid.is_deleted(): item['status'] = None item['errors'] = [_('Item already DELETED' ' in the system')] result.append(item) continue else: exist_url = request.url_root + \ 'records/' + item_exist.get('recid') if item.get('uri') == exist_url: item['status'] = 'update' else: item['errors'] = ['URI of items are not match'] item['status'] = None else: item['id'] = None if item.get('uri'): item['errors'] = ['Item has no ID but non-empty URI'] item['status'] = None except PIDDoesNotExistError: pass except BaseException: current_app.logger.error( 'Unexpected error: ', sys.exc_info()[0] ) if item.get('status') == 'new': handle_remove_identifier(item) result.append(item) return result
def compare_identifier(item, item_exist): """Compare data is Identifier. :argument item -- {dict} item import. item_exist -- {dict} item in system. :return return -- Name of key if is Identifier. """ if item.get('Identifier key'): item_iden = item.get('metadata', '').get(item.get('Identifier key')) item_exist_iden = item_exist.get(item.get( 'Identifier key')).get('attribute_value_mlt') if len(item_iden) == len(item_exist_iden): list_dif = difference(item_iden, item_exist_iden) if list_dif: item['errors'] = ['Errors in Identifier'] item['status'] = '' elif len(item_iden) > len(item_exist_iden): list_dif = difference(item_iden, item_exist_iden) for i in list_dif + item_iden: if i not in item_exist_iden: try: pids = [ k for k in i.values() if k != 'DOI' or k != 'HDL'] for pid in pids: item_check = \ WekoRecord.get_record_by_pid(pid) if item_check and item_check.id != item.id: item['errors'] = ['Errors in Identifier'] item['status'] = '' except BaseException: current_app.logger.error('Unexpected error: ', sys.exc_info()[0]) if item['errors']: item['metadata'][item.get('Identifier key')] = list(set([ it for it in list_dif + item_iden ])) elif len(item_iden) < len(item_exist_iden): item['metadata'][item.get('Identifier key')] = item_exist_iden if item.get('uri'): pass return item
def check_restricted_content(): """Check if record has restricted content for current user. :return: boolean """ if request.headers['Content-Type'] != 'application/json': return abort(400) post_data = request.get_json() restricted_records = set() for record_id in post_data['record_ids']: try: record = WekoRecord.get_record_by_pid(record_id) for file in record.files: if not check_file_download_permission(record, file.info()): restricted_records.add(record_id) except Exception: pass return jsonify({'restricted_records': list(restricted_records)})
def _export_item(record_id, export_format, include_contents, tmp_path=None, records_data=None): """Exports files for record according to view permissions.""" exported_item = {} record = WekoRecord.get_record_by_pid(record_id) if record: exported_item['record_id'] = record.id exported_item['name'] = 'recid_{}'.format(record_id) exported_item['files'] = [] exported_item['path'] = 'recid_' + str(record_id) exported_item['item_type_id'] = record.get('item_type_id') if not records_data: records_data = record # Create metadata file. with open('{}/{}_metadata.json'.format(tmp_path, exported_item['name']), 'w', encoding='utf8') as output_file: json.dump(records_data, output_file, indent=2, sort_keys=True, ensure_ascii=False) # First get all of the files, checking for permissions while doing so if include_contents: # Get files for file in record.files: # TODO: Temporary processing if check_file_download_permission(record, file.info()): exported_item['files'].append(file.info()) # TODO: Then convert the item into the desired format if file: file_buffered = file.obj.file.storage().open() temp_file = open(tmp_path + '/' + file.obj.basename, 'wb') temp_file.write(file_buffered.read()) temp_file.close() return record, exported_item
def _validation(self, record_id=None): """ Update the index detail info. :param record_id: Identifier of record. :return: Updated Resource info """ from .utils import get_real_path if self.status: if self.repository_id == current_app.config.get( "WEKO_ROOT_INDEX", WEKO_ROOT_INDEX): return True if self.index.public_state: if record_id: record = WekoRecord.get_record_by_pid(record_id) if record and record.get("path"): list_path = get_real_path(record.get("path")) if str(self.repository_id) in list_path: return True else: return True return False
def prepare_edit_item(): """Prepare_edit_item. Host the api which provide 2 service: Create new activity for editing flow Check permission: check if user is owner/admin/shared user request: header: Content type must be json data: pid_value: pid_value return: The result json: code: status code, msg: meassage result, data: url redirect """ def _get_workflow_by_item_type_id(item_type_name_id, item_type_id): """Get workflow settings by item type id.""" workflow = WorkFlow.query.filter_by(itemtype_id=item_type_id).first() if not workflow: item_type_list = ItemTypes.get_by_name_id(item_type_name_id) id_list = [x.id for x in item_type_list] workflow = (WorkFlow.query.filter( WorkFlow.itemtype_id.in_(id_list)).order_by( WorkFlow.itemtype_id.desc()).order_by( WorkFlow.flow_id.asc()).first()) return workflow if request.headers['Content-Type'] != 'application/json': """Check header of request""" return jsonify(code=-1, msg=_('Header Error')) post_activity = request.get_json() pid_value = post_activity.get('pid_value') if pid_value: try: record = WekoRecord.get_record_by_pid(pid_value) owner = str(record.get('owner')) shared_id = str(record.get('weko_shared_id')) user_id = str(get_current_user()) is_admin = get_user_roles() activity = WorkActivity() pid_object = PersistentIdentifier.get('recid', pid_value) latest_pid = PIDVersioning(child=pid_object).last_child # check user's permission if user_id != owner and not is_admin[0] and user_id != shared_id: return jsonify( code=-1, msg=_(r"You are not allowed to edit this item.")) lists = ItemTypes.get_latest() if not lists: return jsonify(code=-1, msg=_(r"You do not even have an Itemtype.")) item_type_id = record.get('item_type_id') item_type = ItemTypes.get_by_id(item_type_id) if not item_type: return jsonify(code=-1, msg=_(r"This itemtype isn't found.")) # check item is being editied item_id = latest_pid.object_uuid workflow_activity = activity.get_workflow_activity_by_item_id( item_id) if not workflow_activity: # get workflow of first record attached version ID: x.1 workflow_activity = activity.get_workflow_activity_by_item_id( pid_object.object_uuid) # if workflow of the item is not found # use default settings of item type to which the item belongs else: # show error when has stt is Begin or Doing if workflow_activity.action_status == \ ActionStatusPolicy.ACTION_BEGIN \ or workflow_activity.action_status == \ ActionStatusPolicy.ACTION_DOING: return jsonify(code=-1, msg=_(r"The workflow is being edited.")) # prepare params for new workflow activity if workflow_activity: post_activity['workflow_id'] = workflow_activity.workflow_id post_activity['flow_id'] = workflow_activity.flow_id else: workflow = _get_workflow_by_item_type_id( item_type.name_id, item_type_id) if not workflow: return jsonify(code=-1, msg=_('Workflow setting does not exist.')) post_activity['workflow_id'] = workflow.id post_activity['flow_id'] = workflow.flow_id post_activity['itemtype_id'] = item_type_id getargs = request.args community = getargs.get('community', None) # Create a new version of a record. record = WekoDeposit.get_record(item_id) if not record: return jsonify(code=-1, msg=_('Record does not exist.')) deposit = WekoDeposit(record, record.model) draft_record = deposit.newversion(pid_object) if not draft_record: return jsonify(code=-1, msg=_('An error has occurred.')) # Create snapshot bucket for draft record from invenio_records_files.models import RecordsBuckets try: with db.session.begin_nested(): from weko_workflow.utils import delete_bucket draft_deposit = WekoDeposit(draft_record, draft_record.model) snapshot = record.files.bucket.snapshot(lock=False) snapshot.locked = False draft_deposit['_buckets'] = {'deposit': str(snapshot.id)} draft_record_bucket = RecordsBuckets.create( record=draft_record.model, bucket=snapshot) # Remove duplicated buckets draft_record_buckets = RecordsBuckets.query.filter_by( record_id=draft_record.model.id).all() for record_bucket in draft_record_buckets: if record_bucket != draft_record_bucket: delete_bucket_id = record_bucket.bucket_id RecordsBuckets.query.filter_by( bucket_id=delete_bucket_id).delete() delete_bucket(delete_bucket_id) draft_deposit.commit() except Exception as ex: db.session.rollback() current_app.logger.exception(str(ex)) return jsonify(code=-1, msg=_('An error has occurred.')) # Create a new workflow activity. rtn = activity.init_activity(post_activity, community, draft_record.model.id) if rtn: # GOTO: TEMPORARY EDIT MODE FOR IDENTIFIER identifier_actionid = get_actionid('identifier_grant') if workflow_activity: identifier = activity.get_action_identifier_grant( workflow_activity.activity_id, identifier_actionid) else: identifier = activity.get_action_identifier_grant( '', identifier_actionid) if identifier: if identifier.get('action_identifier_select') > \ IDENTIFIER_GRANT_DOI: identifier['action_identifier_select'] = \ IDENTIFIER_GRANT_CAN_WITHDRAW elif identifier.get('action_identifier_select') == \ IDENTIFIER_GRANT_IS_WITHDRAWING: identifier['action_identifier_select'] = \ IDENTIFIER_GRANT_WITHDRAWN activity.create_or_update_action_identifier( rtn.activity_id, identifier_actionid, identifier) mail_list = FeedbackMailList.get_mail_list_by_item_id( item_id=pid_object.object_uuid) if mail_list: activity.create_or_update_action_feedbackmail( activity_id=rtn.activity_id, action_id=ITEM_REGISTRATION_ACTION_ID, feedback_maillist=mail_list) if community: comm = GetCommunity.get_community_by_id(community) url_redirect = url_for('weko_workflow.display_activity', activity_id=rtn.activity_id, community=comm.id) else: url_redirect = url_for('weko_workflow.display_activity', activity_id=rtn.activity_id) return jsonify(code=0, msg='success', data={'redirect': url_redirect}) except Exception as e: current_app.logger.error('Unexpected error: ', str(e)) return jsonify(code=-1, msg=_('An error has occurred.'))
def iframe_items_index(pid_value='0'): """Iframe items index.""" try: if pid_value == '0' or pid_value == 0: return redirect(url_for('.iframe_index')) record = WekoRecord.get_record_by_pid(pid_value) action = 'private' if record.get('publish_status', '1') == '1' \ else 'publish' community_id = session.get('itemlogin_community_id') ctx = {'community': None} if community_id: comm = GetCommunity.get_community_by_id(community_id) ctx = {'community': comm} if request.method == 'GET': cur_activity = session['itemlogin_activity'] # If enable auto set index feature # and activity is usage application item type steps = session['itemlogin_steps'] contain_application_endpoint = False for step in steps: if step.get('ActionEndpoint') == 'item_login_application': contain_application_endpoint = True enable_auto_set_index = current_app.config.get( 'WEKO_WORKFLOW_ENABLE_AUTO_SET_INDEX_FOR_ITEM_TYPE') if enable_auto_set_index and contain_application_endpoint: index_id = get_index_id(cur_activity.activity_id) update_index_tree_for_record(pid_value, index_id) return redirect(url_for('weko_workflow.iframe_success')) # Get the design for widget rendering from weko_theme.utils import get_design_layout page, render_widgets = get_design_layout( community_id or current_app.config['WEKO_THEME_DEFAULT_COMMUNITY']) return render_template('weko_items_ui/iframe/item_index.html', page=page, render_widgets=render_widgets, pid_value=pid_value, action=action, activity=session['itemlogin_activity'], item=session['itemlogin_item'], steps=session['itemlogin_steps'], action_id=session['itemlogin_action_id'], cur_step=session['itemlogin_cur_step'], record=session['itemlogin_record'], histories=session['itemlogin_histories'], res_check=session['itemlogin_res_check'], pid=session['itemlogin_pid'], community_id=community_id, **ctx) if request.headers['Content-Type'] != 'application/json': flash(_('Invalid Request'), 'error') from weko_theme.utils import get_design_layout page, render_widgets = get_design_layout( current_app.config['WEKO_THEME_DEFAULT_COMMUNITY']) return render_template('weko_items_ui/iframe/item_index.html', page=page, render_widgets=render_widgets, community_id=community_id, **ctx) data = request.get_json() sessionstore = RedisStore( redis.StrictRedis.from_url('redis://{host}:{port}/1'.format( host=os.getenv('INVENIO_REDIS_HOST', 'localhost'), port=os.getenv('INVENIO_REDIS_PORT', '6379')))) if request.method == 'PUT': """update index of item info.""" item_str = sessionstore.get('item_index_{}'.format(pid_value)) sessionstore.delete('item_index_{}'.format(pid_value)) item = json.loads(item_str) item['index'] = data elif request.method == 'POST': """update item data info.""" sessionstore.put('item_index_{}'.format(pid_value), json.dumps(data), ttl_secs=300) return jsonify(data) except BaseException: current_app.logger.error('Unexpected error: ', sys.exc_info()[0]) return abort(400)
def get_change_dump_manifest_xml(self, record_id): """Get change dump manifest xml. :param record_id: Identifier of record :return xml """ if not self._is_record_in_index(record_id) or not self._validation(): return None cdm = ChangeDumpManifest() cdm.up = '{}resync/{}/changedump.xml'.format(request.url_root, self.repository_id) if self.change_dump_manifest: prev_id, prev_ver_id = record_id.split(".") current_record = WekoRecord.get_record_by_pid(record_id) from .utils import get_pid prev_record_pid = get_pid('{}.{}'.format(prev_id, str(int(prev_ver_id) - 1))) if prev_record_pid: prev_record = WekoRecord.get_record( id_=prev_record_pid.object_uuid) else: prev_record = None if current_record: list_file = [file for file in current_record.files] current_checksum = [ file.info().get('checksum') for file in current_record.files ] prev_checksum = [] if prev_record: list_file.extend([file for file in prev_record.files]) prev_checksum = [ file.info().get('checksum') for file in prev_record.files ] for file in list_file: file_info = file.info() change = None if file_info.get('checksum') in prev_checksum: if file_info.get('checksum') in current_checksum: change = None if file_info.get('checksum') not in current_checksum: change = 'deleted' else: if file_info.get('checksum') in current_checksum: change = 'created' path = 'recid_{}/{}'.format(current_record.get('recid'), file_info.get('key')) lastmod = str(datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc).isoformat()) if change: re = Resource( '{}record/{}/files/{}'.format( request.url_root, current_record.get('recid'), file_info.get('key')), lastmod=lastmod, sha256=file_info.get('checksum').split(':')[1], length=str(file_info.get('size')), path=path if change != 'delete' else '', change=change) cdm.add(re) return cdm.as_xml()
def prepare_edit_item(): """Prepare_edit_item. Host the api which provide 2 service: Create new activity for editing flow Check permission: check if user is owner/admin/shared user request: header: Content type must be json data: pid_value: pid_value return: The result json: code: status code, msg: meassage result, data: url redirect """ if request.headers['Content-Type'] != 'application/json': """Check header of request""" return jsonify(code=-1, msg=_('Header Error')) post_activity = request.get_json() pid_value = post_activity.get('pid_value') if pid_value: try: record = WekoRecord.get_record_by_pid(pid_value) owner = str(record.get('owner')) shared_id = str(record.get('weko_shared_id')) user_id = str(get_current_user()) is_admin = get_user_roles() activity = WorkActivity() pid_object = PersistentIdentifier.get('recid', pid_value) # check item is being editied item_id = pid_object.object_uuid workflow_action_stt = \ activity.get_workflow_activity_status_by_item_id( item_id=item_id) # show error when has stt is Begin or Doing if workflow_action_stt is not None and \ (workflow_action_stt == ActionStatusPolicy.ACTION_BEGIN or workflow_action_stt == ActionStatusPolicy.ACTION_DOING): return jsonify(code=-13, msg=_('The workflow is being edited. ')) if user_id != owner and not is_admin[0] and user_id != shared_id: return jsonify(code=-1, msg=_('You are not allowed to edit this item.')) lists = ItemTypes.get_latest() if not lists: return jsonify(code=-1, msg=_('You do not even have an itemtype.')) item_type_id = record.get('item_type_id') item_type = ItemTypes.get_by_id(item_type_id) if item_type is None: return jsonify(code=-1, msg=_('This itemtype not found.')) upt_current_activity = activity.upt_activity_detail( item_id=pid_object.object_uuid) if upt_current_activity is not None: post_activity['workflow_id'] = upt_current_activity.workflow_id post_activity['flow_id'] = upt_current_activity.flow_id post_activity['itemtype_id'] = item_type_id getargs = request.args community = getargs.get('community', None) # Create a new version of a record. record = WekoDeposit.get_record(item_id) if record is None: return jsonify(code=-1, msg=_('Record does not exist.')) deposit = WekoDeposit(record, record.model) new_record = deposit.newversion(pid_object) if new_record is None: return jsonify(code=-1, msg=_('An error has occurred.')) rtn = activity.init_activity(post_activity, community, new_record.model.id) if rtn: # GOTO: TEMPORARY EDIT MODE FOR IDENTIFIER identifier_actionid = get_actionid('identifier_grant') identifier = activity.get_action_identifier_grant( upt_current_activity.activity_id, identifier_actionid) if identifier: if identifier.get('action_identifier_select') > \ IDENTIFIER_GRANT_DOI: identifier['action_identifier_select'] = \ IDENTIFIER_GRANT_CAN_WITHDRAW elif identifier.get('action_identifier_select') == \ IDENTIFIER_GRANT_IS_WITHDRAWING: identifier['action_identifier_select'] = \ IDENTIFIER_GRANT_WITHDRAWN activity.create_or_update_action_identifier( rtn.activity_id, identifier_actionid, identifier) if community: comm = GetCommunity.get_community_by_id(community) url_redirect = url_for( 'weko_workflow.display_activity', activity_id=rtn.activity_id, community=comm.id) else: url_redirect = url_for( 'weko_workflow.display_activity', activity_id=rtn.activity_id) return jsonify(code=0, msg='success', data={'redirect': url_redirect}) except Exception as e: current_app.logger.error('Unexpected error: ', str(e)) return jsonify(code=-1, msg=_('An error has occurred.'))