Coverage for src / mesh / ojs / import_submission.py: 0%

125 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-08-11 09:47 +0000

1import datetime 

2import logging 

3from typing import TYPE_CHECKING 

4 

5from django.db import transaction 

6 

7from mesh.models import EditorialDecision, Submission, SubmissionAuthor, SubmissionVersion, User 

8from mesh.models.crud import create_submission 

9from mesh.models.orm.submission_models import SubmissionState 

10from mesh.ojs import ( 

11 NSMAP, 

12 OJS_STATUS_MAPPING, 

13) 

14from mesh.ojs.import_file import ( 

15 import_ojs_file, 

16 version_additional_file_factory, 

17 version_main_file_factory, 

18) 

19from mesh.ojs.import_review import import_ojs_reviews 

20from mesh.ojs.import_round import ( 

21 import_ojs_round_decision, 

22 import_ojs_round_files, 

23) 

24from mesh.ojs.import_user import get_user_or_create_fake_one 

25from mesh.ojs.lxml_utils import ( 

26 find_and_make_test_email_or_raise, 

27 find_or_raise, 

28 find_xpath_or_raise, 

29 findall_xpath, 

30 findtext_or_raise, 

31 get_and_make_test_email_or_raise, 

32 get_or_raise, 

33 str_to_datetime, 

34) 

35from mesh.views.utils import assign_editor 

36 

37if TYPE_CHECKING: 

38 from lxml import etree 

39 

40 from mesh.models import JournalSection 

41 

42logger = logging.getLogger(__name__) 

43 

44 

45def import_ojs_article( 

46 extended_article: "etree._Element", 

47 journal_section: "JournalSection", 

48 path: str, 

49 duplicate_nb: int = 1, 

50): 

51 duplicate_id: str = "" 

52 if duplicate_nb > 1: 

53 duplicate_id = f"_{duplicate_nb}" 

54 

55 ojs_publication = find_or_raise(extended_article, "publication") 

56 owner_author_id = ojs_publication.get("primary_contact_id") 

57 if not owner_author_id: 

58 raise ValueError("Publication does not have any owner") 

59 ojs_authors = ojs_publication.findall("authors/author", NSMAP) 

60 

61 # AUTHORS 

62 # keys are ojs author ID 

63 authors: dict[str, SubmissionAuthor] = {} 

64 for ojs_author in ojs_authors: 

65 # TODO : should we handle user_group_ref here ? 

66 ojs_author_id = get_or_raise(ojs_author, "id") 

67 

68 authors[ojs_author_id] = SubmissionAuthor( 

69 email=find_and_make_test_email_or_raise(ojs_author, "email") + duplicate_id, 

70 first_name=findtext_or_raise(ojs_author, "givenname"), 

71 last_name=findtext_or_raise(ojs_author, "familyname"), 

72 ) 

73 

74 if owner_author_id not in authors: 

75 raise ValueError("Publication's primary_contact_id not found in authors") 

76 author_owner = authors[owner_author_id] 

77 

78 try: 

79 created_by_user = get_user_or_create_fake_one(email=author_owner.email.lower()) 

80 except User.DoesNotExist: 

81 raise ValueError( 

82 f"OJS ID : {findtext_or_raise(extended_article, 'id')}| User associated with author does not exists" 

83 ) 

84 

85 # SUBMISSION 

86 # submitted_event_log = find_or_raise( 

87 # extended_article, "event_logs/event_log[@message='submission.event.submissionSubmitted']" 

88 # ) 

89 submission_date_submitted = str_to_datetime(get_or_raise(extended_article, "date_submitted")) 

90 

91 # first_event_log = find_xpath_or_raise(extended_article, "ojs:event_logs/ojs:event_log[last()]") 

92 # submission_date_created = str_to_datetime(get_or_raise(first_event_log, "date_logged")) 

93 

94 try: 

95 with transaction.atomic(): 

96 submission = Submission.objects.get( 

97 ojs_id=findtext_or_raise(extended_article, "id") + duplicate_id, 

98 journal_section=journal_section, 

99 ) 

100 submission.delete() 

101 except Submission.DoesNotExist: 

102 pass 

103 submission = create_submission(user=created_by_user, date=submission_date_submitted) 

104 

105 submission.ojs_id = int(findtext_or_raise(extended_article, "id")) 

106 submission.journal_section = journal_section 

107 submission.created_by = created_by_user 

108 submission.name = findtext_or_raise(ojs_publication, "title") 

109 try: 

110 submission.abstract = findtext_or_raise(ojs_publication, "abstract") 

111 except ValueError: 

112 submission.abstract = "" 

113 submission.author_agreement = True 

114 for author in authors.values(): 

115 author.submission = submission 

116 author.save() 

117 

118 # EDITORS 

119 ojs_submission_stage = find_or_raise(extended_article, "stage[@path='submission']") 

120 for participant in ojs_submission_stage.findall("participant", NSMAP): 

121 if get_or_raise(participant, "user_group_ref") not in ( 

122 "Managing editor", 

123 "Section editor", 

124 "Associate editor", 

125 ): 

126 continue 

127 # if get_or_raise(participant, "recommend_only") == 1: 

128 # continue 

129 # if get_or_raise(participant, "can_change_metadata") == 0: 

130 # continue 

131 email = get_and_make_test_email_or_raise(participant, "user_email").lower() 

132 user = get_user_or_create_fake_one(email) 

133 if not user: 

134 logger.error(f"Cannot assign {email} as editor : user does not exists in database") 

135 continue 

136 

137 # Find editor assigned event log 

138 try: 

139 event_log = find_xpath_or_raise( 

140 extended_article, 

141 f"ojs:event_logs/ojs:event_log[@message='submission.event.participantAdded'][@participant_email='{email}']", 

142 ) 

143 editor_assigned_date = str_to_datetime(get_or_raise(event_log, "date_logged")) 

144 manager_email = get_and_make_test_email_or_raise(event_log, "user_email") 

145 manager = User.objects.filter(email=manager_email).first() 

146 except BaseException: 

147 logger.debug(f"Couldn't find editor assigned date for {email}") 

148 editor_assigned_date = submission_date_submitted + datetime.timedelta(0, 1) 

149 manager = None 

150 

151 if not submission.editors.filter(user=user).exists(): 

152 assign_editor( 

153 submission, 

154 user, 

155 manager=manager, 

156 date=editor_assigned_date, 

157 ) 

158 

159 # First version 

160 # Submission main file 

161 submission_main_file_search = extended_article.findall( 

162 "submission_file[@stage='submission'][@genre='Manuscript']", NSMAP 

163 ) 

164 if len(submission_main_file_search) > 1: 

165 raise ValueError("Found multiple main file candidates") 

166 if len(submission_main_file_search) == 0: 

167 raise ValueError("Couldn't find main file") 

168 submission_main_file = submission_main_file_search[0] 

169 current_version = submission.versions.first() 

170 if current_version is None: 

171 raise ValueError("Submission does not have a current version") 

172 

173 import_ojs_file( 

174 submission_main_file, 

175 version_main_file_factory(current_version), 

176 created_by_user, 

177 path, 

178 ) 

179 

180 # Submission additional files 

181 submission_files = findall_xpath( 

182 extended_article, "ojs:submission_file[@stage='submission'][not(@genre='Manuscript')]" 

183 ) 

184 for submission_file_element in submission_files: 

185 import_ojs_file( 

186 submission_file_element, 

187 version_additional_file_factory(current_version), 

188 created_by_user, 

189 path, 

190 ) 

191 submission.submit(user=created_by_user, date_submitted=submission_date_submitted) 

192 # Rounds 

193 rounds_list = list( 

194 extended_article.findall("stage[@path='externalReview']/review_round", NSMAP) 

195 ) 

196 rounds_list.sort(key=lambda review_round: get_or_raise(review_round, "round")) 

197 # OJS allows authors to send answers to reviews without creating a new round 

198 # This doesn't works with MESH, so we have to create a fictional submission at the end instead 

199 # Every round is thus shifted by +1 

200 if len(rounds_list) > 0: 

201 # Pseudo Do While 

202 round = rounds_list.pop(0) 

203 current_version = submission.versions.first() 

204 if current_version is None: 

205 raise ValueError("Submission does not have a current version") 

206 import_ojs_reviews(round, current_version, path) 

207 # import_ojs_round_decision(round, submission, current_version) 

208 decision = EditorialDecision(value=SubmissionState.REVISION_REQUESTED.value) 

209 decision.version = current_version 

210 decision.save() 

211 # TODO : extract real date 

212 submission.apply_editorial_decision(decision, None, date=submission_date_submitted) 

213 

214 current_version = SubmissionVersion(submission=submission) 

215 current_version.save() 

216 submission.refresh_from_db() 

217 # Those files are the files that the reviewer have access to 

218 import_ojs_round_files(round, current_version, path, created_by_user) 

219 previous_round = round 

220 for round in rounds_list: 

221 # TODO : extract real date 

222 submission.submit(user=created_by_user, date_submitted=submission_date_submitted) 

223 current_version.refresh_from_db() 

224 # TODO : extract real date 

225 submission.override_saved_date(date_last_modified=submission_date_submitted) 

226 import_ojs_reviews(round, current_version, path) 

227 import_ojs_round_decision(previous_round, submission, current_version) 

228 

229 current_version = SubmissionVersion(submission=submission) 

230 current_version.save() 

231 submission.refresh_from_db() 

232 # Those files are the files that the author sent as an answer to the previous round 

233 # Since we do not have those in MESH, those are now the files for the next round 

234 import_ojs_round_files( 

235 round, current_version, path, created_by_user, stage="review_revision" 

236 ) 

237 previous_round = round 

238 

239 submission.submit(user=created_by_user, date_submitted=submission_date_submitted) 

240 import_ojs_round_decision(previous_round, submission, current_version) 

241 # STATE 

242 ojs_status = get_or_raise(extended_article, "hr_status") 

243 

244 # TODO : Better submission state handling 

245 if ojs_status not in OJS_STATUS_MAPPING: 

246 raise ValueError(f"Publication status {ojs_status} not recognized") 

247 submission.state = OJS_STATUS_MAPPING[ojs_status].value 

248 

249 submission.save() 

250 return submission