|
9 | 9 |
|
10 | 10 | from bs4 import BeautifulSoup, ParserRejectedMarkup
|
11 | 11 | from openpyxl import load_workbook
|
| 12 | +from sqlalchemy import insert |
12 | 13 |
|
13 | 14 | from .base import BaseMultiUpdater, DocumentInfo
|
14 | 15 | from ..database import DocumentType, LunchMenu, SnackMenu
|
@@ -85,7 +86,9 @@ def get_document_effective(self, document: DocumentInfo) -> datetime.date:
|
85 | 86 |
|
86 | 87 | # jedilnik-kosilo-YYYY-MM-DD(-popravek).pdf
|
87 | 88 | # jedilnik-malica-YYYY-MM-DD(-popravek).pdf
|
88 |
| - date = re.search(r"jedilnik-(?:kosilo|malica)-(\d+)-(\d+)-(\d+)(?:-[\w-]*)?.pdf", document.url) |
| 89 | + date = re.search( |
| 90 | + r"jedilnik-(?:kosilo|malica)-(\d+)-(\d+)-(\d+)(?:-[\w-]*)?\.(?:pdf|xlsx)", document.url |
| 91 | + ) |
89 | 92 |
|
90 | 93 | # The specified date is commonly Monday of the effective week
|
91 | 94 | # However, in some cases, it may also be another day of that week
|
@@ -174,68 +177,67 @@ def _parse_snack_menu_xlsx(self, stream: BytesIO, effective: datetime.date) -> N
|
174 | 177 | # Extract workbook from an XLSX stream
|
175 | 178 | wb = with_span(op="extract")(load_workbook)(stream, read_only=True, data_only=True)
|
176 | 179 |
|
177 |
| - menu: dict[str, Any] = {} |
| 180 | + snack_menu: dict[str, Any] = { |
| 181 | + "normal": [], |
| 182 | + "poultry": [], |
| 183 | + "vegetarian": [], |
| 184 | + "fruitvegetable": [], |
| 185 | + } |
178 | 186 | days = 0
|
179 | 187 |
|
180 |
| - # Parse tables into menus and store them |
| 188 | + # Parse menus and store them |
181 | 189 | for ws in wb:
|
182 |
| - for wr in ws.iter_rows(min_row=1, max_col=3): |
183 |
| - if not hasattr(wr[0].border, "bottom"): |
| 190 | + for wr in ws.iter_rows(min_row=2, max_col=5): |
| 191 | + if days == 5: |
| 192 | + break |
| 193 | + |
| 194 | + # Ignore blank cells |
| 195 | + if not wr[1].value: |
184 | 196 | continue
|
185 | 197 |
|
186 |
| - # Make mypy not complain about incorrect types for cell values |
187 |
| - # If the cell has an incorrect type, we should fail anyway |
| 198 | + # Check for correct cell value type (else mypy complains) |
188 | 199 | if typing.TYPE_CHECKING:
|
189 | 200 | assert isinstance(wr[1].value, str)
|
190 | 201 | assert isinstance(wr[2].value, str)
|
191 | 202 | assert isinstance(wr[3].value, str)
|
192 | 203 | assert isinstance(wr[4].value, str)
|
193 | 204 |
|
194 |
| - # Store the menu after the end of table |
195 |
| - if wr[0].border.bottom.color: |
196 |
| - if menu and menu["date"]: |
197 |
| - # fmt: off |
198 |
| - model = ( |
199 |
| - self.session.query(SnackMenu) |
200 |
| - .filter(SnackMenu.date == menu["date"]) |
201 |
| - .first() |
202 |
| - ) |
203 |
| - # fmt: on |
204 |
| - |
205 |
| - if not model: |
206 |
| - model = SnackMenu() |
207 |
| - |
208 |
| - model.date = menu["date"] |
209 |
| - model.normal = "\n".join(menu["normal"][1:]) |
210 |
| - model.poultry = "\n".join(menu["poultry"][1:]) |
211 |
| - model.vegetarian = "\n".join(menu["vegetarian"][1:]) |
212 |
| - model.fruitvegetable = "\n".join(menu["fruitvegetable"][1:]) |
213 |
| - |
214 |
| - self.session.add(model) |
215 |
| - days += 1 |
216 |
| - |
217 |
| - menu = { |
218 |
| - "date": None, |
219 |
| - "normal": [], |
220 |
| - "poultry": [], |
221 |
| - "vegetarian": [], |
222 |
| - "fruitvegetable": [], |
223 |
| - } |
224 |
| - |
225 |
| - if wr[0].value and isinstance(wr[0].value, datetime.datetime): |
226 |
| - menu["date"] = effective + datetime.timedelta(days=days) |
| 205 | + # Ignore information cells |
| 206 | + if "NV in N" in wr[1].value: |
| 207 | + continue |
227 | 208 |
|
228 | 209 | if wr[1].value:
|
229 |
| - menu["normal"].append(wr[1].value.strip()) |
| 210 | + snack_menu["normal"].append(wr[1].value.strip()) |
230 | 211 |
|
231 | 212 | if wr[2].value:
|
232 |
| - menu["poultry"].append(wr[2].value.strip()) |
| 213 | + snack_menu["poultry"].append(wr[2].value.strip()) |
233 | 214 |
|
234 | 215 | if wr[3].value:
|
235 |
| - menu["vegetarian"].append(wr[3].value.strip()) |
| 216 | + snack_menu["vegetarian"].append(wr[3].value.strip()) |
236 | 217 |
|
237 | 218 | if wr[4].value:
|
238 |
| - menu["fruitvegetable"].append(wr[4].value.strip()) |
| 219 | + snack_menu["fruitvegetable"].append(wr[4].value.strip()) |
| 220 | + |
| 221 | + # Store the menu after the end of day |
| 222 | + if wr[0].border.bottom.color: |
| 223 | + snack_menu["date"] = effective + datetime.timedelta(days=days) |
| 224 | + self.session.query(SnackMenu).filter(SnackMenu.date == snack_menu["date"]).delete() |
| 225 | + |
| 226 | + snack_menu["normal"] = "\n".join(snack_menu["normal"]) |
| 227 | + snack_menu["poultry"] = "\n".join(snack_menu["poultry"]) |
| 228 | + snack_menu["vegetarian"] = "\n".join(snack_menu["vegetarian"]) |
| 229 | + snack_menu["fruitvegetable"] = "\n".join(snack_menu["fruitvegetable"]) |
| 230 | + |
| 231 | + self.session.execute(insert(SnackMenu), snack_menu) |
| 232 | + |
| 233 | + # Set for next day |
| 234 | + days += 1 |
| 235 | + snack_menu = { |
| 236 | + "normal": [], |
| 237 | + "poultry": [], |
| 238 | + "vegetarian": [], |
| 239 | + "fruitvegetable": [], |
| 240 | + } |
239 | 241 |
|
240 | 242 | wb.close()
|
241 | 243 |
|
@@ -278,56 +280,53 @@ def _parse_lunch_menu_xlsx(self, stream: BytesIO, effective: datetime.date) -> N
|
278 | 280 | # Extract workbook from an XLSX stream
|
279 | 281 | wb = with_span(op="extract")(load_workbook)(stream, read_only=True, data_only=True)
|
280 | 282 |
|
281 |
| - menu: dict[str, Any] = {} |
| 283 | + lunch_menu: dict[str, Any] = { |
| 284 | + "normal": [], |
| 285 | + "vegetarian": [], |
| 286 | + } |
282 | 287 | days = 0
|
283 | 288 |
|
284 |
| - # Parse tables into menus and store them |
| 289 | + # Parse menus and store them |
285 | 290 | for ws in wb:
|
286 |
| - for wr in ws.iter_rows(min_row=1, max_col=3): |
287 |
| - if not hasattr(wr[0].border, "bottom"): |
| 291 | + for wr in ws.iter_rows(min_row=2, max_col=3): |
| 292 | + if days == 5: |
| 293 | + break |
| 294 | + |
| 295 | + # Ignore blank cells |
| 296 | + if not wr[1].value: |
288 | 297 | continue
|
289 | 298 |
|
290 |
| - # Make mypy not complain about incorrect types for cell values |
291 |
| - # If the cell has an incorrect type, we should fail anyway |
| 299 | + # Check for correct cell value type (else mypy complains) |
292 | 300 | if typing.TYPE_CHECKING:
|
293 | 301 | assert isinstance(wr[1].value, str)
|
294 | 302 | assert isinstance(wr[2].value, str)
|
295 | 303 |
|
296 |
| - # Store the menu after the end of table |
297 |
| - if wr[0].border.bottom.color: |
298 |
| - if menu and menu["date"]: |
299 |
| - # fmt: off |
300 |
| - model = ( |
301 |
| - self.session.query(LunchMenu) |
302 |
| - .filter(LunchMenu.date == menu["date"]) |
303 |
| - .first() |
304 |
| - ) |
305 |
| - # fmt: on |
306 |
| - |
307 |
| - if not model: |
308 |
| - model = LunchMenu() |
309 |
| - |
310 |
| - model.date = menu["date"] |
311 |
| - model.normal = "\n".join(menu["normal"][1:]) |
312 |
| - model.vegetarian = "\n".join(menu["vegetarian"][1:]) |
313 |
| - |
314 |
| - self.session.add(model) |
315 |
| - days += 1 |
316 |
| - |
317 |
| - menu = { |
318 |
| - "date": None, |
319 |
| - "normal": [], |
320 |
| - "vegetarian": [], |
321 |
| - } |
322 |
| - |
323 |
| - if wr[0].value and isinstance(wr[0].value, datetime.datetime): |
324 |
| - menu["date"] = effective + datetime.timedelta(days=days) |
| 304 | + # Ignore information cells |
| 305 | + if "N KOSILO" in wr[1].value: |
| 306 | + continue |
325 | 307 |
|
326 | 308 | if wr[1].value:
|
327 |
| - menu["normal"].append(wr[1].value.strip()) |
| 309 | + lunch_menu["normal"].append(wr[1].value.strip()) |
328 | 310 |
|
329 | 311 | if wr[2].value:
|
330 |
| - menu["vegetarian"].append(wr[2].value.strip()) |
| 312 | + lunch_menu["vegetarian"].append(wr[2].value.strip()) |
| 313 | + |
| 314 | + # Store the menu after the end of day |
| 315 | + if wr[0].border.bottom.color: |
| 316 | + lunch_menu["date"] = effective + datetime.timedelta(days=days) |
| 317 | + self.session.query(LunchMenu).filter(LunchMenu.date == lunch_menu["date"]).delete() |
| 318 | + |
| 319 | + lunch_menu["normal"] = "\n".join(lunch_menu["normal"]) |
| 320 | + lunch_menu["vegetarian"] = "\n".join(lunch_menu["vegetarian"]) |
| 321 | + |
| 322 | + self.session.execute(insert(LunchMenu), lunch_menu) |
| 323 | + |
| 324 | + # Set for next day |
| 325 | + days += 1 |
| 326 | + lunch_menu = { |
| 327 | + "normal": [], |
| 328 | + "vegetarian": [], |
| 329 | + } |
331 | 330 |
|
332 | 331 | wb.close()
|
333 | 332 |
|
|
0 commit comments