patchman 4.0.8__tar.gz → 4.0.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. {patchman-4.0.8 → patchman-4.0.10}/PKG-INFO +1 -1
  2. patchman-4.0.10/VERSION.txt +1 -0
  3. patchman-4.0.10/arch/migrations/0002_alter_machinearchitecture_options_and_more.py +21 -0
  4. patchman-4.0.10/domains/migrations/0002_alter_domain_options.py +17 -0
  5. {patchman-4.0.8 → patchman-4.0.10}/errata/apps.py +16 -0
  6. {patchman-4.0.8 → patchman-4.0.10}/errata/managers.py +1 -1
  7. patchman-4.0.10/errata/migrations/0008_add_cached_count_fields.py +38 -0
  8. patchman-4.0.10/errata/migrations/0009_backfill_cached_counts.py +45 -0
  9. {patchman-4.0.8 → patchman-4.0.10}/errata/models.py +8 -3
  10. patchman-4.0.10/errata/signals.py +60 -0
  11. {patchman-4.0.8 → patchman-4.0.10}/errata/tables.py +5 -5
  12. {patchman-4.0.8 → patchman-4.0.10}/errata/templates/errata/erratum_detail.html +6 -4
  13. patchman-4.0.10/errata/tests/test_models.py +241 -0
  14. {patchman-4.0.8 → patchman-4.0.10}/errata/views.py +1 -1
  15. {patchman-4.0.8 → patchman-4.0.10}/hosts/apps.py +3 -0
  16. {patchman-4.0.8 → patchman-4.0.10}/hosts/managers.py +1 -1
  17. patchman-4.0.10/hosts/migrations/0010_alter_hostrepo_options.py +17 -0
  18. patchman-4.0.10/hosts/migrations/0011_host_bug_updates_count_host_errata_count_and_more.py +33 -0
  19. patchman-4.0.10/hosts/migrations/0012_backfill_cached_counts.py +33 -0
  20. {patchman-4.0.8 → patchman-4.0.10}/hosts/models.py +18 -10
  21. {patchman-4.0.8 → patchman-4.0.10}/hosts/serializers.py +2 -2
  22. patchman-4.0.10/hosts/signals.py +45 -0
  23. {patchman-4.0.8 → patchman-4.0.10}/hosts/tables.py +1 -1
  24. patchman-4.0.10/hosts/tasks.py +43 -0
  25. {patchman-4.0.8 → patchman-4.0.10}/hosts/utils.py +46 -0
  26. {patchman-4.0.8 → patchman-4.0.10}/hosts/views.py +10 -14
  27. {patchman-4.0.8 → patchman-4.0.10}/modules/managers.py +1 -1
  28. {patchman-4.0.8 → patchman-4.0.10}/modules/views.py +2 -2
  29. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/apps.py +3 -0
  30. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/forms.py +1 -1
  31. patchman-4.0.10/operatingsystems/migrations/0009_osvariant_hosts_count.py +18 -0
  32. patchman-4.0.10/operatingsystems/migrations/0010_backfill_osvariant_counts.py +29 -0
  33. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/models.py +2 -0
  34. patchman-4.0.10/operatingsystems/signals.py +56 -0
  35. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/templates/operatingsystems/osrelease_detail.html +6 -4
  36. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/templates/operatingsystems/osvariant_delete_multiple.html +1 -1
  37. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/templates/operatingsystems/osvariant_detail.html +1 -1
  38. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/views.py +8 -8
  39. {patchman-4.0.8 → patchman-4.0.10}/packages/managers.py +1 -1
  40. patchman-4.0.10/packages/migrations/0006_alter_packageupdate_options.py +17 -0
  41. {patchman-4.0.8 → patchman-4.0.10}/packages/utils.py +41 -29
  42. {patchman-4.0.8 → patchman-4.0.10}/packages/views.py +5 -5
  43. {patchman-4.0.8 → patchman-4.0.10}/patchman/settings.py +1 -0
  44. {patchman-4.0.8 → patchman-4.0.10}/patchman.egg-info/PKG-INFO +1 -1
  45. {patchman-4.0.8 → patchman-4.0.10}/patchman.egg-info/SOURCES.txt +18 -0
  46. {patchman-4.0.8 → patchman-4.0.10}/reports/views.py +12 -24
  47. {patchman-4.0.8 → patchman-4.0.10}/repos/apps.py +3 -0
  48. {patchman-4.0.8 → patchman-4.0.10}/repos/forms.py +2 -2
  49. {patchman-4.0.8 → patchman-4.0.10}/repos/managers.py +1 -1
  50. patchman-4.0.10/repos/migrations/0007_alter_mirror_options_alter_mirrorpackage_options_and_more.py +25 -0
  51. patchman-4.0.10/repos/migrations/0008_mirror_packages_count.py +18 -0
  52. patchman-4.0.10/repos/migrations/0009_backfill_mirror_counts.py +27 -0
  53. {patchman-4.0.8 → patchman-4.0.10}/repos/models.py +10 -16
  54. patchman-4.0.10/repos/signals.py +28 -0
  55. {patchman-4.0.8 → patchman-4.0.10}/repos/tables.py +1 -1
  56. {patchman-4.0.8 → patchman-4.0.10}/repos/utils.py +3 -2
  57. {patchman-4.0.8 → patchman-4.0.10}/repos/views.py +14 -18
  58. {patchman-4.0.8 → patchman-4.0.10}/sbin/patchman +3 -50
  59. {patchman-4.0.8 → patchman-4.0.10}/scripts/rpm-post-install.sh +0 -1
  60. {patchman-4.0.8 → patchman-4.0.10}/security/managers.py +1 -1
  61. patchman-4.0.10/security/migrations/0008_alter_cwe_options_alter_reference_options.py +21 -0
  62. {patchman-4.0.8 → patchman-4.0.10}/security/tasks.py +2 -2
  63. {patchman-4.0.8 → patchman-4.0.10}/security/utils.py +2 -2
  64. {patchman-4.0.8 → patchman-4.0.10}/security/views.py +3 -3
  65. {patchman-4.0.8 → patchman-4.0.10}/util/__init__.py +1 -1
  66. {patchman-4.0.8 → patchman-4.0.10}/util/context_processors.py +2 -2
  67. {patchman-4.0.8 → patchman-4.0.10}/util/management/commands/revoke_api_key.py +1 -1
  68. {patchman-4.0.8 → patchman-4.0.10}/util/templates/dashboard.html +6 -2
  69. {patchman-4.0.8 → patchman-4.0.10}/util/templatetags/common.py +3 -6
  70. {patchman-4.0.8 → patchman-4.0.10}/util/views.py +33 -8
  71. patchman-4.0.8/VERSION.txt +0 -1
  72. patchman-4.0.8/errata/tests/test_models.py +0 -122
  73. patchman-4.0.8/hosts/tasks.py +0 -80
  74. {patchman-4.0.8 → patchman-4.0.10}/AUTHORS +0 -0
  75. {patchman-4.0.8 → patchman-4.0.10}/COPYING +0 -0
  76. {patchman-4.0.8 → patchman-4.0.10}/INSTALL.md +0 -0
  77. {patchman-4.0.8 → patchman-4.0.10}/MANIFEST.in +0 -0
  78. {patchman-4.0.8 → patchman-4.0.10}/README.md +0 -0
  79. {patchman-4.0.8 → patchman-4.0.10}/arch/__init__.py +0 -0
  80. {patchman-4.0.8 → patchman-4.0.10}/arch/admin.py +0 -0
  81. {patchman-4.0.8 → patchman-4.0.10}/arch/apps.py +0 -0
  82. {patchman-4.0.8 → patchman-4.0.10}/arch/migrations/0001_initial.py +0 -0
  83. {patchman-4.0.8 → patchman-4.0.10}/arch/migrations/__init__.py +0 -0
  84. {patchman-4.0.8 → patchman-4.0.10}/arch/models.py +0 -0
  85. {patchman-4.0.8 → patchman-4.0.10}/arch/serializers.py +0 -0
  86. {patchman-4.0.8 → patchman-4.0.10}/arch/tests/__init__.py +0 -0
  87. {patchman-4.0.8 → patchman-4.0.10}/arch/tests/test_api.py +0 -0
  88. {patchman-4.0.8 → patchman-4.0.10}/arch/tests/test_models.py +0 -0
  89. {patchman-4.0.8 → patchman-4.0.10}/arch/utils.py +0 -0
  90. {patchman-4.0.8 → patchman-4.0.10}/arch/views.py +0 -0
  91. {patchman-4.0.8 → patchman-4.0.10}/domains/__init__.py +0 -0
  92. {patchman-4.0.8 → patchman-4.0.10}/domains/admin.py +0 -0
  93. {patchman-4.0.8 → patchman-4.0.10}/domains/apps.py +0 -0
  94. {patchman-4.0.8 → patchman-4.0.10}/domains/migrations/0001_initial.py +0 -0
  95. {patchman-4.0.8 → patchman-4.0.10}/domains/migrations/__init__.py +0 -0
  96. {patchman-4.0.8 → patchman-4.0.10}/domains/models.py +0 -0
  97. {patchman-4.0.8 → patchman-4.0.10}/domains/serializers.py +0 -0
  98. {patchman-4.0.8 → patchman-4.0.10}/domains/tests/__init__.py +0 -0
  99. {patchman-4.0.8 → patchman-4.0.10}/domains/tests/test_api.py +0 -0
  100. {patchman-4.0.8 → patchman-4.0.10}/domains/tests/test_models.py +0 -0
  101. {patchman-4.0.8 → patchman-4.0.10}/domains/views.py +0 -0
  102. {patchman-4.0.8 → patchman-4.0.10}/errata/__init__.py +0 -0
  103. {patchman-4.0.8 → patchman-4.0.10}/errata/admin.py +0 -0
  104. {patchman-4.0.8 → patchman-4.0.10}/errata/migrations/0001_initial.py +0 -0
  105. {patchman-4.0.8 → patchman-4.0.10}/errata/migrations/0002_alter_erratumreference_unique_together.py +0 -0
  106. {patchman-4.0.8 → patchman-4.0.10}/errata/migrations/0003_delete_erratumreference_alter_erratum_references.py +0 -0
  107. {patchman-4.0.8 → patchman-4.0.10}/errata/migrations/0004_rename_packages_erratum_fixed_packages.py +0 -0
  108. {patchman-4.0.8 → patchman-4.0.10}/errata/migrations/0005_erratum_affected_packages_and_more.py +0 -0
  109. {patchman-4.0.8 → patchman-4.0.10}/errata/migrations/0006_alter_erratum_options.py +0 -0
  110. {patchman-4.0.8 → patchman-4.0.10}/errata/migrations/0007_alter_erratum_fixed_packages.py +0 -0
  111. {patchman-4.0.8 → patchman-4.0.10}/errata/migrations/__init__.py +0 -0
  112. {patchman-4.0.8 → patchman-4.0.10}/errata/serializers.py +0 -0
  113. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/__init__.py +0 -0
  114. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/distros/__init__.py +0 -0
  115. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/distros/alma.py +0 -0
  116. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/distros/arch.py +0 -0
  117. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/distros/centos.py +0 -0
  118. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/distros/debian.py +0 -0
  119. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/distros/rocky.py +0 -0
  120. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/distros/ubuntu.py +0 -0
  121. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/repos/__init__.py +0 -0
  122. {patchman-4.0.8 → patchman-4.0.10}/errata/sources/repos/yum.py +0 -0
  123. {patchman-4.0.8 → patchman-4.0.10}/errata/tasks.py +0 -0
  124. {patchman-4.0.8 → patchman-4.0.10}/errata/templates/errata/erratum_list.html +0 -0
  125. {patchman-4.0.8 → patchman-4.0.10}/errata/tests/__init__.py +0 -0
  126. {patchman-4.0.8 → patchman-4.0.10}/errata/tests/test_api.py +0 -0
  127. {patchman-4.0.8 → patchman-4.0.10}/errata/tests/test_integration.py +0 -0
  128. {patchman-4.0.8 → patchman-4.0.10}/errata/urls.py +0 -0
  129. {patchman-4.0.8 → patchman-4.0.10}/errata/utils.py +0 -0
  130. {patchman-4.0.8 → patchman-4.0.10}/etc/patchman/apache.conf.example +0 -0
  131. {patchman-4.0.8 → patchman-4.0.10}/etc/patchman/celery.conf +0 -0
  132. {patchman-4.0.8 → patchman-4.0.10}/etc/patchman/local_settings.py +0 -0
  133. {patchman-4.0.8 → patchman-4.0.10}/etc/systemd/system/patchman-celery-beat.service +0 -0
  134. {patchman-4.0.8 → patchman-4.0.10}/etc/systemd/system/patchman-celery-worker.service +0 -0
  135. {patchman-4.0.8 → patchman-4.0.10}/hosts/__init__.py +0 -0
  136. {patchman-4.0.8 → patchman-4.0.10}/hosts/admin.py +0 -0
  137. {patchman-4.0.8 → patchman-4.0.10}/hosts/forms.py +0 -0
  138. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0001_initial.py +0 -0
  139. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0002_initial.py +0 -0
  140. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0003_host_modules.py +0 -0
  141. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0004_remove_host_tags_host_tags.py +0 -0
  142. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0005_rename_os_host_osvariant.py +0 -0
  143. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0006_migrate_to_tz_aware.py +0 -0
  144. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0007_alter_host_tags.py +0 -0
  145. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0008_alter_host_options.py +0 -0
  146. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/0009_host_errata.py +0 -0
  147. {patchman-4.0.8 → patchman-4.0.10}/hosts/migrations/__init__.py +0 -0
  148. {patchman-4.0.8 → patchman-4.0.10}/hosts/templates/hosts/host_delete.html +0 -0
  149. {patchman-4.0.8 → patchman-4.0.10}/hosts/templates/hosts/host_detail.html +0 -0
  150. {patchman-4.0.8 → patchman-4.0.10}/hosts/templates/hosts/host_edit.html +0 -0
  151. {patchman-4.0.8 → patchman-4.0.10}/hosts/templates/hosts/host_list.html +0 -0
  152. {patchman-4.0.8 → patchman-4.0.10}/hosts/templatetags/__init__.py +0 -0
  153. {patchman-4.0.8 → patchman-4.0.10}/hosts/templatetags/report_alert.py +0 -0
  154. {patchman-4.0.8 → patchman-4.0.10}/hosts/tests/__init__.py +0 -0
  155. {patchman-4.0.8 → patchman-4.0.10}/hosts/tests/test_api.py +0 -0
  156. {patchman-4.0.8 → patchman-4.0.10}/hosts/tests/test_find_updates.py +0 -0
  157. {patchman-4.0.8 → patchman-4.0.10}/hosts/tests/test_managers.py +0 -0
  158. {patchman-4.0.8 → patchman-4.0.10}/hosts/tests/test_models.py +0 -0
  159. {patchman-4.0.8 → patchman-4.0.10}/hosts/urls.py +0 -0
  160. {patchman-4.0.8 → patchman-4.0.10}/modules/__init__.py +0 -0
  161. {patchman-4.0.8 → patchman-4.0.10}/modules/admin.py +0 -0
  162. {patchman-4.0.8 → patchman-4.0.10}/modules/apps.py +0 -0
  163. {patchman-4.0.8 → patchman-4.0.10}/modules/migrations/0001_initial.py +0 -0
  164. {patchman-4.0.8 → patchman-4.0.10}/modules/migrations/0002_auto_20240204_2214.py +0 -0
  165. {patchman-4.0.8 → patchman-4.0.10}/modules/migrations/0003_alter_module_options.py +0 -0
  166. {patchman-4.0.8 → patchman-4.0.10}/modules/migrations/0004_alter_module_options.py +0 -0
  167. {patchman-4.0.8 → patchman-4.0.10}/modules/migrations/0005_alter_module_unique_together.py +0 -0
  168. {patchman-4.0.8 → patchman-4.0.10}/modules/migrations/__init__.py +0 -0
  169. {patchman-4.0.8 → patchman-4.0.10}/modules/models.py +0 -0
  170. {patchman-4.0.8 → patchman-4.0.10}/modules/serializers.py +0 -0
  171. {patchman-4.0.8 → patchman-4.0.10}/modules/tables.py +0 -0
  172. {patchman-4.0.8 → patchman-4.0.10}/modules/templates/modules/module_detail.html +0 -0
  173. {patchman-4.0.8 → patchman-4.0.10}/modules/templates/modules/module_list.html +0 -0
  174. {patchman-4.0.8 → patchman-4.0.10}/modules/urls.py +0 -0
  175. {patchman-4.0.8 → patchman-4.0.10}/modules/utils.py +0 -0
  176. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/__init__.py +0 -0
  177. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/admin.py +0 -0
  178. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/fixtures/os.json +0 -0
  179. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/fixtures/osgroup.json +0 -0
  180. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/managers.py +0 -0
  181. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0001_initial.py +0 -0
  182. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0002_initial.py +0 -0
  183. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0003_os_arch.py +0 -0
  184. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0003_osgroup_codename.py +0 -0
  185. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0004_alter_osgroup_unique_together.py +0 -0
  186. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0005_rename_osgroup_osrelease_rename_os_osvariant_and_more.py +0 -0
  187. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0006_osrelease_cpe_name_osvariant_codename.py +0 -0
  188. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0007_alter_osrelease_unique_together.py +0 -0
  189. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/0008_alter_osrelease_options_alter_osvariant_options.py +0 -0
  190. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/migrations/__init__.py +0 -0
  191. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/serializers.py +0 -0
  192. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/tables.py +0 -0
  193. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/templates/operatingsystems/os_landing.html +0 -0
  194. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/templates/operatingsystems/osrelease_delete.html +0 -0
  195. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/templates/operatingsystems/osrelease_list.html +0 -0
  196. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/templates/operatingsystems/osvariant_delete.html +0 -0
  197. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/templates/operatingsystems/osvariant_list.html +0 -0
  198. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/tests/__init__.py +0 -0
  199. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/tests/test_api.py +0 -0
  200. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/tests/test_models.py +0 -0
  201. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/urls.py +0 -0
  202. {patchman-4.0.8 → patchman-4.0.10}/operatingsystems/utils.py +0 -0
  203. {patchman-4.0.8 → patchman-4.0.10}/packages/__init__.py +0 -0
  204. {patchman-4.0.8 → patchman-4.0.10}/packages/admin.py +0 -0
  205. {patchman-4.0.8 → patchman-4.0.10}/packages/apps.py +0 -0
  206. {patchman-4.0.8 → patchman-4.0.10}/packages/migrations/0001_initial.py +0 -0
  207. {patchman-4.0.8 → patchman-4.0.10}/packages/migrations/0002_auto_20250207_1319.py +0 -0
  208. {patchman-4.0.8 → patchman-4.0.10}/packages/migrations/0002_delete_erratum_delete_erratumreference.py +0 -0
  209. {patchman-4.0.8 → patchman-4.0.10}/packages/migrations/0003_auto_20250207_1746.py +0 -0
  210. {patchman-4.0.8 → patchman-4.0.10}/packages/migrations/0004_alter_package_options_alter_packagecategory_options_and_more.py +0 -0
  211. {patchman-4.0.8 → patchman-4.0.10}/packages/migrations/0005_alter_package_packagetype.py +0 -0
  212. {patchman-4.0.8 → patchman-4.0.10}/packages/migrations/__init__.py +0 -0
  213. {patchman-4.0.8 → patchman-4.0.10}/packages/models.py +0 -0
  214. {patchman-4.0.8 → patchman-4.0.10}/packages/serializers.py +0 -0
  215. {patchman-4.0.8 → patchman-4.0.10}/packages/tables.py +0 -0
  216. {patchman-4.0.8 → patchman-4.0.10}/packages/templates/packages/package_detail.html +0 -0
  217. {patchman-4.0.8 → patchman-4.0.10}/packages/templates/packages/package_list.html +0 -0
  218. {patchman-4.0.8 → patchman-4.0.10}/packages/templates/packages/package_name_detail.html +0 -0
  219. {patchman-4.0.8 → patchman-4.0.10}/packages/templates/packages/package_name_list.html +0 -0
  220. {patchman-4.0.8 → patchman-4.0.10}/packages/tests/__init__.py +0 -0
  221. {patchman-4.0.8 → patchman-4.0.10}/packages/tests/test_api.py +0 -0
  222. {patchman-4.0.8 → patchman-4.0.10}/packages/tests/test_models.py +0 -0
  223. {patchman-4.0.8 → patchman-4.0.10}/packages/tests/test_version_compare.py +0 -0
  224. {patchman-4.0.8 → patchman-4.0.10}/packages/urls.py +0 -0
  225. {patchman-4.0.8 → patchman-4.0.10}/patchman/__init__.py +0 -0
  226. {patchman-4.0.8 → patchman-4.0.10}/patchman/celery.py +0 -0
  227. {patchman-4.0.8 → patchman-4.0.10}/patchman/receivers.py +0 -0
  228. {patchman-4.0.8 → patchman-4.0.10}/patchman/signals.py +0 -0
  229. {patchman-4.0.8 → patchman-4.0.10}/patchman/sqlite3/base.py +0 -0
  230. {patchman-4.0.8 → patchman-4.0.10}/patchman/static/css/base.css +0 -0
  231. {patchman-4.0.8 → patchman-4.0.10}/patchman/static/js/expandable-text.js +0 -0
  232. {patchman-4.0.8 → patchman-4.0.10}/patchman/static/js/sidebar.js +0 -0
  233. {patchman-4.0.8 → patchman-4.0.10}/patchman/urls.py +0 -0
  234. {patchman-4.0.8 → patchman-4.0.10}/patchman/wsgi.py +0 -0
  235. {patchman-4.0.8 → patchman-4.0.10}/patchman.egg-info/dependency_links.txt +0 -0
  236. {patchman-4.0.8 → patchman-4.0.10}/patchman.egg-info/requires.txt +0 -0
  237. {patchman-4.0.8 → patchman-4.0.10}/patchman.egg-info/top_level.txt +0 -0
  238. {patchman-4.0.8 → patchman-4.0.10}/reports/__init__.py +0 -0
  239. {patchman-4.0.8 → patchman-4.0.10}/reports/admin.py +0 -0
  240. {patchman-4.0.8 → patchman-4.0.10}/reports/apps.py +0 -0
  241. {patchman-4.0.8 → patchman-4.0.10}/reports/migrations/0001_initial.py +0 -0
  242. {patchman-4.0.8 → patchman-4.0.10}/reports/migrations/0002_report_modules.py +0 -0
  243. {patchman-4.0.8 → patchman-4.0.10}/reports/migrations/0003_remove_report_accessed.py +0 -0
  244. {patchman-4.0.8 → patchman-4.0.10}/reports/migrations/0004_migrate_to_tz_aware.py +0 -0
  245. {patchman-4.0.8 → patchman-4.0.10}/reports/migrations/0005_alter_report_options.py +0 -0
  246. {patchman-4.0.8 → patchman-4.0.10}/reports/migrations/__init__.py +0 -0
  247. {patchman-4.0.8 → patchman-4.0.10}/reports/models.py +0 -0
  248. {patchman-4.0.8 → patchman-4.0.10}/reports/serializers.py +0 -0
  249. {patchman-4.0.8 → patchman-4.0.10}/reports/tables.py +0 -0
  250. {patchman-4.0.8 → patchman-4.0.10}/reports/tasks.py +0 -0
  251. {patchman-4.0.8 → patchman-4.0.10}/reports/templates/reports/report.txt +0 -0
  252. {patchman-4.0.8 → patchman-4.0.10}/reports/templates/reports/report_delete.html +0 -0
  253. {patchman-4.0.8 → patchman-4.0.10}/reports/templates/reports/report_detail.html +0 -0
  254. {patchman-4.0.8 → patchman-4.0.10}/reports/templates/reports/report_list.html +0 -0
  255. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/__init__.py +0 -0
  256. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/test_api.py +0 -0
  257. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/test_edge_cases.py +0 -0
  258. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/test_integration.py +0 -0
  259. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/test_models.py +0 -0
  260. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/test_parsing.py +0 -0
  261. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/test_serializers.py +0 -0
  262. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/test_tasks.py +0 -0
  263. {patchman-4.0.8 → patchman-4.0.10}/reports/tests/test_utils.py +0 -0
  264. {patchman-4.0.8 → patchman-4.0.10}/reports/urls.py +0 -0
  265. {patchman-4.0.8 → patchman-4.0.10}/reports/utils.py +0 -0
  266. {patchman-4.0.8 → patchman-4.0.10}/repos/__init__.py +0 -0
  267. {patchman-4.0.8 → patchman-4.0.10}/repos/admin.py +0 -0
  268. {patchman-4.0.8 → patchman-4.0.10}/repos/migrations/0001_initial.py +0 -0
  269. {patchman-4.0.8 → patchman-4.0.10}/repos/migrations/0002_alter_repository_repotype.py +0 -0
  270. {patchman-4.0.8 → patchman-4.0.10}/repos/migrations/0003_migrate_to_tz_aware.py +0 -0
  271. {patchman-4.0.8 → patchman-4.0.10}/repos/migrations/0004_rename_file_checksum_mirror_package_checksum.py +0 -0
  272. {patchman-4.0.8 → patchman-4.0.10}/repos/migrations/0005_rename_package_checksum_mirror_packages_checksum.py +0 -0
  273. {patchman-4.0.8 → patchman-4.0.10}/repos/migrations/0006_mirror_errata_checksum_mirror_modules_checksum.py +0 -0
  274. {patchman-4.0.8 → patchman-4.0.10}/repos/migrations/__init__.py +0 -0
  275. {patchman-4.0.8 → patchman-4.0.10}/repos/repo_types/__init__.py +0 -0
  276. {patchman-4.0.8 → patchman-4.0.10}/repos/repo_types/arch.py +0 -0
  277. {patchman-4.0.8 → patchman-4.0.10}/repos/repo_types/deb.py +0 -0
  278. {patchman-4.0.8 → patchman-4.0.10}/repos/repo_types/gentoo.py +0 -0
  279. {patchman-4.0.8 → patchman-4.0.10}/repos/repo_types/rpm.py +0 -0
  280. {patchman-4.0.8 → patchman-4.0.10}/repos/repo_types/yast.py +0 -0
  281. {patchman-4.0.8 → patchman-4.0.10}/repos/repo_types/yum.py +0 -0
  282. {patchman-4.0.8 → patchman-4.0.10}/repos/serializers.py +0 -0
  283. {patchman-4.0.8 → patchman-4.0.10}/repos/tasks.py +0 -0
  284. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/mirror_delete.html +0 -0
  285. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/mirror_detail.html +0 -0
  286. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/mirror_edit.html +0 -0
  287. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/mirror_list.html +0 -0
  288. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/mirror_with_repo_list.html +0 -0
  289. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/repo_delete.html +0 -0
  290. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/repo_detail.html +0 -0
  291. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/repo_edit.html +0 -0
  292. {patchman-4.0.8 → patchman-4.0.10}/repos/templates/repos/repo_list.html +0 -0
  293. {patchman-4.0.8 → patchman-4.0.10}/repos/templatetags/__init__.py +0 -0
  294. {patchman-4.0.8 → patchman-4.0.10}/repos/tests/__init__.py +0 -0
  295. {patchman-4.0.8 → patchman-4.0.10}/repos/tests/test_api.py +0 -0
  296. {patchman-4.0.8 → patchman-4.0.10}/repos/tests/test_managers.py +0 -0
  297. {patchman-4.0.8 → patchman-4.0.10}/repos/tests/test_mirror_sync.py +0 -0
  298. {patchman-4.0.8 → patchman-4.0.10}/repos/tests/test_models.py +0 -0
  299. {patchman-4.0.8 → patchman-4.0.10}/repos/urls.py +0 -0
  300. {patchman-4.0.8 → patchman-4.0.10}/requirements.txt +0 -0
  301. {patchman-4.0.8 → patchman-4.0.10}/sbin/patchman-manage +0 -0
  302. {patchman-4.0.8 → patchman-4.0.10}/sbin/patchman-set-secret-key +0 -0
  303. {patchman-4.0.8 → patchman-4.0.10}/scripts/clear-django-logs.py +0 -0
  304. {patchman-4.0.8 → patchman-4.0.10}/scripts/create_graph.sh +0 -0
  305. {patchman-4.0.8 → patchman-4.0.10}/scripts/rpm-install.sh +0 -0
  306. {patchman-4.0.8 → patchman-4.0.10}/security/__init__.py +0 -0
  307. {patchman-4.0.8 → patchman-4.0.10}/security/admin.py +0 -0
  308. {patchman-4.0.8 → patchman-4.0.10}/security/apps.py +0 -0
  309. {patchman-4.0.8 → patchman-4.0.10}/security/migrations/0001_initial.py +0 -0
  310. {patchman-4.0.8 → patchman-4.0.10}/security/migrations/0002_alter_cve_options.py +0 -0
  311. {patchman-4.0.8 → patchman-4.0.10}/security/migrations/0003_alter_cve_description_alter_cwe_description.py +0 -0
  312. {patchman-4.0.8 → patchman-4.0.10}/security/migrations/0004_alter_cve_options.py +0 -0
  313. {patchman-4.0.8 → patchman-4.0.10}/security/migrations/0005_reference_cve_references.py +0 -0
  314. {patchman-4.0.8 → patchman-4.0.10}/security/migrations/0006_alter_cve_options_alter_cvss_unique_together.py +0 -0
  315. {patchman-4.0.8 → patchman-4.0.10}/security/migrations/0007_remove_cve_title.py +0 -0
  316. {patchman-4.0.8 → patchman-4.0.10}/security/migrations/__init__.py +0 -0
  317. {patchman-4.0.8 → patchman-4.0.10}/security/models.py +0 -0
  318. {patchman-4.0.8 → patchman-4.0.10}/security/serializers.py +0 -0
  319. {patchman-4.0.8 → patchman-4.0.10}/security/tables.py +0 -0
  320. {patchman-4.0.8 → patchman-4.0.10}/security/templates/security/cve_detail.html +0 -0
  321. {patchman-4.0.8 → patchman-4.0.10}/security/templates/security/cve_list.html +0 -0
  322. {patchman-4.0.8 → patchman-4.0.10}/security/templates/security/cwe_detail.html +0 -0
  323. {patchman-4.0.8 → patchman-4.0.10}/security/templates/security/cwe_list.html +0 -0
  324. {patchman-4.0.8 → patchman-4.0.10}/security/templates/security/reference_list.html +0 -0
  325. {patchman-4.0.8 → patchman-4.0.10}/security/templates/security/security_landing.html +0 -0
  326. {patchman-4.0.8 → patchman-4.0.10}/security/tests/__init__.py +0 -0
  327. {patchman-4.0.8 → patchman-4.0.10}/security/tests/test_api.py +0 -0
  328. {patchman-4.0.8 → patchman-4.0.10}/security/tests/test_models.py +0 -0
  329. {patchman-4.0.8 → patchman-4.0.10}/security/urls.py +0 -0
  330. {patchman-4.0.8 → patchman-4.0.10}/setup.cfg +0 -0
  331. {patchman-4.0.8 → patchman-4.0.10}/setup.py +0 -0
  332. {patchman-4.0.8 → patchman-4.0.10}/util/apps.py +0 -0
  333. {patchman-4.0.8 → patchman-4.0.10}/util/filterspecs.py +0 -0
  334. {patchman-4.0.8 → patchman-4.0.10}/util/logging.py +0 -0
  335. {patchman-4.0.8 → patchman-4.0.10}/util/management/__init__.py +0 -0
  336. {patchman-4.0.8 → patchman-4.0.10}/util/management/commands/__init__.py +0 -0
  337. {patchman-4.0.8 → patchman-4.0.10}/util/management/commands/create_api_key.py +0 -0
  338. {patchman-4.0.8 → patchman-4.0.10}/util/management/commands/list_api_keys.py +0 -0
  339. {patchman-4.0.8 → patchman-4.0.10}/util/tables.py +0 -0
  340. {patchman-4.0.8 → patchman-4.0.10}/util/tasks.py +0 -0
  341. {patchman-4.0.8 → patchman-4.0.10}/util/templates/404.html +0 -0
  342. {patchman-4.0.8 → patchman-4.0.10}/util/templates/500.html +0 -0
  343. {patchman-4.0.8 → patchman-4.0.10}/util/templates/base.html +0 -0
  344. {patchman-4.0.8 → patchman-4.0.10}/util/templates/bulk_actions.html +0 -0
  345. {patchman-4.0.8 → patchman-4.0.10}/util/templates/navbar.html +0 -0
  346. {patchman-4.0.8 → patchman-4.0.10}/util/templates/objectlist.html +0 -0
  347. {patchman-4.0.8 → patchman-4.0.10}/util/templates/registration/login.html +0 -0
  348. {patchman-4.0.8 → patchman-4.0.10}/util/templates/searchbar.html +0 -0
  349. {patchman-4.0.8 → patchman-4.0.10}/util/templates/table.html +0 -0
  350. {patchman-4.0.8 → patchman-4.0.10}/util/templatetags/__init__.py +0 -0
  351. {patchman-4.0.8 → patchman-4.0.10}/util/tests/__init__.py +0 -0
  352. {patchman-4.0.8 → patchman-4.0.10}/util/tests/test_commands.py +0 -0
  353. {patchman-4.0.8 → patchman-4.0.10}/util/tests/test_utils.py +0 -0
  354. {patchman-4.0.8 → patchman-4.0.10}/util/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchman
3
- Version: 4.0.8
3
+ Version: 4.0.10
4
4
  Summary: Django based patch status monitoring tool for linux systems
5
5
  Home-page: http://patchman.openbytes.ie/
6
6
  Author: Marcus Furlong
@@ -0,0 +1 @@
1
+ 4.0.10
@@ -0,0 +1,21 @@
1
+ # Generated by Django 4.2.28 on 2026-02-11 04:55
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('arch', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name='machinearchitecture',
15
+ options={'ordering': ['name'], 'verbose_name': 'Machine Architecture'},
16
+ ),
17
+ migrations.AlterModelOptions(
18
+ name='packagearchitecture',
19
+ options={'ordering': ['name'], 'verbose_name': 'Package Architecture'},
20
+ ),
21
+ ]
@@ -0,0 +1,17 @@
1
+ # Generated by Django 4.2.28 on 2026-02-11 04:55
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('domains', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name='domain',
15
+ options={'ordering': ['name']},
16
+ ),
17
+ ]
@@ -19,3 +19,19 @@ from django.apps import AppConfig
19
19
 
20
20
  class ErrataConfig(AppConfig):
21
21
  name = 'errata'
22
+
23
+ def ready(self):
24
+ from datetime import timedelta
25
+
26
+ from django.db.models.signals import post_save
27
+ from django.utils import timezone
28
+
29
+ import errata.signals # noqa: F401
30
+
31
+ def set_initial_last_run(sender, instance, created, **kwargs):
32
+ if created and instance.name == 'update_errata_cves_cwes_every_12_hours':
33
+ instance.last_run_at = timezone.now() - timedelta(days=1)
34
+ instance.save(update_fields=['last_run_at'])
35
+
36
+ from django_celery_beat.models import PeriodicTask
37
+ post_save.connect(set_initial_last_run, sender=PeriodicTask)
@@ -19,4 +19,4 @@ from django.db import models
19
19
 
20
20
  class ErratumManager(models.Manager):
21
21
  def get_queryset(self):
22
- return super().get_queryset().select_related()
22
+ return super().get_queryset()
@@ -0,0 +1,38 @@
1
+ # Generated by Django 4.2.28 on 2026-02-13 06:44
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('errata', '0007_alter_erratum_fixed_packages'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='erratum',
15
+ name='affected_packages_count',
16
+ field=models.PositiveIntegerField(default=0),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='erratum',
20
+ name='cves_count',
21
+ field=models.PositiveIntegerField(default=0),
22
+ ),
23
+ migrations.AddField(
24
+ model_name='erratum',
25
+ name='fixed_packages_count',
26
+ field=models.PositiveIntegerField(default=0),
27
+ ),
28
+ migrations.AddField(
29
+ model_name='erratum',
30
+ name='osreleases_count',
31
+ field=models.PositiveIntegerField(default=0),
32
+ ),
33
+ migrations.AddField(
34
+ model_name='erratum',
35
+ name='references_count',
36
+ field=models.PositiveIntegerField(default=0),
37
+ ),
38
+ ]
@@ -0,0 +1,45 @@
1
+ # Copyright 2026 Marcus Furlong <furlongm@gmail.com>
2
+ #
3
+ # This file is part of Patchman.
4
+ #
5
+ # Patchman is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, version 3 only.
8
+ #
9
+ # Patchman is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Patchman. If not, see <http://www.gnu.org/licenses/>
16
+
17
+ from django.db import migrations
18
+
19
+
20
+ def backfill_counts(apps, schema_editor):
21
+ Erratum = apps.get_model('errata', 'Erratum')
22
+ for erratum in Erratum.objects.all().iterator():
23
+ erratum.affected_packages_count = erratum.affected_packages.count()
24
+ erratum.fixed_packages_count = erratum.fixed_packages.count()
25
+ erratum.osreleases_count = erratum.osreleases.count()
26
+ erratum.cves_count = erratum.cves.count()
27
+ erratum.references_count = erratum.references.count()
28
+ erratum.save(update_fields=[
29
+ 'affected_packages_count',
30
+ 'fixed_packages_count',
31
+ 'osreleases_count',
32
+ 'cves_count',
33
+ 'references_count',
34
+ ])
35
+
36
+
37
+ class Migration(migrations.Migration):
38
+
39
+ dependencies = [
40
+ ('errata', '0008_add_cached_count_fields'),
41
+ ]
42
+
43
+ operations = [
44
+ migrations.RunPython(backfill_counts, migrations.RunPython.noop),
45
+ ]
@@ -40,6 +40,11 @@ class Erratum(models.Model):
40
40
  osreleases = models.ManyToManyField(OSRelease, blank=True)
41
41
  cves = models.ManyToManyField(CVE, blank=True)
42
42
  references = models.ManyToManyField(Reference, blank=True)
43
+ affected_packages_count = models.PositiveIntegerField(default=0)
44
+ fixed_packages_count = models.PositiveIntegerField(default=0)
45
+ osreleases_count = models.PositiveIntegerField(default=0)
46
+ cves_count = models.PositiveIntegerField(default=0)
47
+ references_count = models.PositiveIntegerField(default=0)
43
48
 
44
49
  objects = ErratumManager()
45
50
 
@@ -49,9 +54,9 @@ class Erratum(models.Model):
49
54
  ordering = ['-issue_date', 'name']
50
55
 
51
56
  def __str__(self):
52
- text = f'{self.name} ({self.e_type}), {self.cves.count()} related CVEs, '
53
- text += f'affecting {self.osreleases.count()} OS Releases, '
54
- text += f'providing {self.fixed_packages.count()} fixed Packages'
57
+ text = f'{self.name} ({self.e_type}), {self.cves_count} related CVEs, '
58
+ text += f'affecting {self.osreleases_count} OS Releases, '
59
+ text += f'providing {self.fixed_packages_count} fixed Packages'
55
60
  return text
56
61
 
57
62
  def get_absolute_url(self):
@@ -0,0 +1,60 @@
1
+ # Copyright 2026 Marcus Furlong <furlongm@gmail.com>
2
+ #
3
+ # This file is part of Patchman.
4
+ #
5
+ # Patchman is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, version 3 only.
8
+ #
9
+ # Patchman is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Patchman. If not, see <http://www.gnu.org/licenses/>
16
+
17
+ from django.db.models.signals import m2m_changed
18
+ from django.dispatch import receiver
19
+
20
+ from errata.models import Erratum
21
+
22
+
23
+ @receiver(m2m_changed, sender=Erratum.affected_packages.through)
24
+ def update_affected_packages_count(sender, instance, action, **kwargs):
25
+ """Update affected_packages_count when Erratum.affected_packages M2M changes."""
26
+ if action in ('post_add', 'post_remove', 'post_clear'):
27
+ instance.affected_packages_count = instance.affected_packages.count()
28
+ instance.save(update_fields=['affected_packages_count'])
29
+
30
+
31
+ @receiver(m2m_changed, sender=Erratum.fixed_packages.through)
32
+ def update_fixed_packages_count(sender, instance, action, **kwargs):
33
+ """Update fixed_packages_count when Erratum.fixed_packages M2M changes."""
34
+ if action in ('post_add', 'post_remove', 'post_clear'):
35
+ instance.fixed_packages_count = instance.fixed_packages.count()
36
+ instance.save(update_fields=['fixed_packages_count'])
37
+
38
+
39
+ @receiver(m2m_changed, sender=Erratum.osreleases.through)
40
+ def update_osreleases_count(sender, instance, action, **kwargs):
41
+ """Update osreleases_count when Erratum.osreleases M2M changes."""
42
+ if action in ('post_add', 'post_remove', 'post_clear'):
43
+ instance.osreleases_count = instance.osreleases.count()
44
+ instance.save(update_fields=['osreleases_count'])
45
+
46
+
47
+ @receiver(m2m_changed, sender=Erratum.cves.through)
48
+ def update_cves_count(sender, instance, action, **kwargs):
49
+ """Update cves_count when Erratum.cves M2M changes."""
50
+ if action in ('post_add', 'post_remove', 'post_clear'):
51
+ instance.cves_count = instance.cves.count()
52
+ instance.save(update_fields=['cves_count'])
53
+
54
+
55
+ @receiver(m2m_changed, sender=Erratum.references.through)
56
+ def update_references_count(sender, instance, action, **kwargs):
57
+ """Update references_count when Erratum.references M2M changes."""
58
+ if action in ('post_add', 'post_remove', 'post_clear'):
59
+ instance.references_count = instance.references.count()
60
+ instance.save(update_fields=['references_count'])
@@ -19,31 +19,31 @@ from util.tables import BaseTable
19
19
 
20
20
  ERRATUM_NAME_TEMPLATE = '<a href="{{ record.get_absolute_url }}">{{ record.name }}</a>'
21
21
  PACKAGES_AFFECTED_TEMPLATE = (
22
- '{% with count=record.affected_packages.count %}'
22
+ '{% with count=record.affected_packages_count %}'
23
23
  '{% if count != 0 %}'
24
24
  '<a href="{% url \'packages:package_list\' %}?erratum_id={{ record.id }}&type=affected">{{ count }}</a>'
25
25
  '{% else %}{% endif %}{% endwith %}'
26
26
  )
27
27
  PACKAGES_FIXED_TEMPLATE = (
28
- '{% with count=record.fixed_packages.count %}'
28
+ '{% with count=record.fixed_packages_count %}'
29
29
  '{% if count != 0 %}'
30
30
  '<a href="{% url \'packages:package_list\' %}?erratum_id={{ record.id }}&type=fixed">{{ count }}</a>'
31
31
  '{% else %}{% endif %}{% endwith %}'
32
32
  )
33
33
  OSRELEASES_TEMPLATE = (
34
- '{% with count=record.osreleases.count %}'
34
+ '{% with count=record.osreleases_count %}'
35
35
  '{% if count != 0 %}'
36
36
  '<a href="{% url \'operatingsystems:osrelease_list\' %}?erratum_id={{ record.id }}">{{ count }}</a>'
37
37
  '{% else %}{% endif %}{% endwith %}'
38
38
  )
39
39
  ERRATUM_CVES_TEMPLATE = (
40
- '{% with count=record.cves.count %}'
40
+ '{% with count=record.cves_count %}'
41
41
  '{% if count != 0 %}'
42
42
  '<a href="{% url \'security:cve_list\' %}?erratum_id={{ record.id }}">{{ count }}</a>'
43
43
  '{% else %}{% endif %}{% endwith %}'
44
44
  )
45
45
  REFERENCES_TEMPLATE = (
46
- '{% with count=record.references.count %}'
46
+ '{% with count=record.references_count %}'
47
47
  '{% if count != 0 %}'
48
48
  '<a href="{% url \'security:reference_list\' %}?erratum_id={{ record.id }}">{{ count }}</a>'
49
49
  '{% else %}{% endif %}{% endwith %}'
@@ -8,10 +8,11 @@
8
8
 
9
9
  {% block content %}
10
10
 
11
+ {% with affected_count=erratum.affected_packages_count fixed_count=erratum.fixed_packages_count %}
11
12
  <ul class="nav nav-tabs">
12
13
  <li class="active"><a data-toggle="tab" href="#erratum_details">Details</a></li>
13
- <li><a data-toggle="tab" href="#erratum_affected_packages">Packages Affected ({{ erratum.affected_packages.count }})</a></li>
14
- <li><a data-toggle="tab" href="#erratum_fixed_packages">Packages Fixed ({{ erratum.fixed_packages.count }})</a></li>
14
+ <li><a data-toggle="tab" href="#erratum_affected_packages">Packages Affected ({{ affected_count }})</a></li>
15
+ <li><a data-toggle="tab" href="#erratum_fixed_packages">Packages Fixed ({{ fixed_count }})</a></li>
15
16
  </ul>
16
17
 
17
18
  <div class="tab-content">
@@ -22,8 +23,8 @@
22
23
  <tr><th class="col-sm-1">Type</th><td> {{ erratum.e_type }} </td></tr>
23
24
  <tr><th class="col-sm-1">Published Date</th><td>{{ erratum.issue_date|date|default_if_none:'' }}</td></tr>
24
25
  <tr><th class="col-sm-1">Synopsis</th><td> {{ erratum.synopsis }} </td></tr>
25
- <tr><th class="col-sm-1">Packages Affected</th><td><a href="{% url 'packages:package_list' %}?erratum_id={{ erratum.id }}&type=affected"> {{ erratum.affected_packages.count }} </a></td></tr>
26
- <tr><th class="col-sm-1">Packages Fixed</th><td><a href="{% url 'packages:package_list' %}?erratum_id={{ erratum.id }}&type=fixed"> {{ erratum.fixed_packages.count }} </a></td></tr>
26
+ <tr><th class="col-sm-1">Packages Affected</th><td><a href="{% url 'packages:package_list' %}?erratum_id={{ erratum.id }}&type=affected"> {{ affected_count }} </a></td></tr>
27
+ <tr><th class="col-sm-1">Packages Fixed</th><td><a href="{% url 'packages:package_list' %}?erratum_id={{ erratum.id }}&type=fixed"> {{ fixed_count }} </a></td></tr>
27
28
  <tr>
28
29
  <th class="col-sm-2">OS Releases Affected</th>
29
30
  <td>
@@ -78,5 +79,6 @@
78
79
  </div>
79
80
  </div>
80
81
  </div>
82
+ {% endwith %}
81
83
 
82
84
  {% endblock %}
@@ -0,0 +1,241 @@
1
+ # Copyright 2025 Marcus Furlong <furlongm@gmail.com>
2
+ #
3
+ # This file is part of Patchman.
4
+ #
5
+ # Patchman is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, version 3 only.
8
+ #
9
+ # Patchman is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Patchman. If not, see <http://www.gnu.org/licenses/>
16
+
17
+ from django.test import TestCase, override_settings
18
+ from django.utils import timezone
19
+
20
+ from errata.models import Erratum
21
+ from operatingsystems.models import OSRelease
22
+ from security.models import CVE, Reference
23
+
24
+
25
+ @override_settings(
26
+ CELERY_TASK_ALWAYS_EAGER=True,
27
+ CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
28
+ )
29
+ class ErratumMethodTests(TestCase):
30
+ """Tests for Erratum model methods."""
31
+
32
+ def test_erratum_creation(self):
33
+ """Test creating an Erratum."""
34
+ erratum = Erratum.objects.create(
35
+ name='USN-1234-1',
36
+ e_type='security',
37
+ synopsis='Security update',
38
+ issue_date=timezone.now(),
39
+ )
40
+ self.assertEqual(erratum.name, 'USN-1234-1')
41
+
42
+ def test_erratum_str(self):
43
+ """Test Erratum __str__ method."""
44
+ erratum = Erratum.objects.create(
45
+ name='USN-1234-1',
46
+ e_type='security',
47
+ synopsis='Security update',
48
+ issue_date=timezone.now(),
49
+ )
50
+ self.assertIn('USN-1234-1', str(erratum))
51
+
52
+ def test_erratum_get_absolute_url(self):
53
+ """Test Erratum.get_absolute_url()."""
54
+ erratum = Erratum.objects.create(
55
+ name='USN-1234-1',
56
+ e_type='security',
57
+ synopsis='Security update',
58
+ issue_date=timezone.now(),
59
+ )
60
+ url = erratum.get_absolute_url()
61
+ self.assertIn(erratum.name, url)
62
+
63
+ def test_erratum_unique_name(self):
64
+ """Test Erratum name is unique."""
65
+ Erratum.objects.create(
66
+ name='USN-1234-1',
67
+ e_type='security',
68
+ synopsis='Security update',
69
+ issue_date=timezone.now(),
70
+ )
71
+ from django.db import IntegrityError
72
+ with self.assertRaises(IntegrityError):
73
+ Erratum.objects.create(
74
+ name='USN-1234-1',
75
+ e_type='bugfix',
76
+ synopsis='Bugfix update',
77
+ issue_date=timezone.now(),
78
+ )
79
+
80
+ def test_erratum_with_cves(self):
81
+ """Test Erratum with associated CVEs."""
82
+ erratum = Erratum.objects.create(
83
+ name='USN-1234-1',
84
+ e_type='security',
85
+ synopsis='Security update',
86
+ issue_date=timezone.now(),
87
+ )
88
+ cve = CVE.objects.create(cve_id='CVE-2024-12345')
89
+ erratum.cves.add(cve)
90
+ self.assertIn(cve, erratum.cves.all())
91
+
92
+ def test_erratum_with_osreleases(self):
93
+ """Test Erratum with associated OS releases."""
94
+ erratum = Erratum.objects.create(
95
+ name='USN-1234-1',
96
+ e_type='security',
97
+ synopsis='Security update',
98
+ issue_date=timezone.now(),
99
+ )
100
+ release = OSRelease.objects.create(name='Ubuntu 22.04')
101
+ erratum.osreleases.add(release)
102
+ self.assertIn(release, erratum.osreleases.all())
103
+
104
+ def test_security_erratum(self):
105
+ """Test creating a security erratum."""
106
+ erratum = Erratum.objects.create(
107
+ name='RHSA-2024:1234',
108
+ e_type='security',
109
+ synopsis='Important security update',
110
+ issue_date=timezone.now(),
111
+ )
112
+ self.assertEqual(erratum.e_type, 'security')
113
+
114
+ def test_bugfix_erratum(self):
115
+ """Test creating a bugfix erratum."""
116
+ erratum = Erratum.objects.create(
117
+ name='RHBA-2024:1234',
118
+ e_type='bugfix',
119
+ synopsis='Bug fix update',
120
+ issue_date=timezone.now(),
121
+ )
122
+ self.assertEqual(erratum.e_type, 'bugfix')
123
+
124
+
125
+ @override_settings(
126
+ CELERY_TASK_ALWAYS_EAGER=True,
127
+ CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
128
+ )
129
+ class ErratumCachedCountTests(TestCase):
130
+ """Tests for Erratum cached count fields and M2M signals."""
131
+
132
+ def setUp(self):
133
+ self.erratum = Erratum.objects.create(
134
+ name='USN-5678-1',
135
+ e_type='security',
136
+ synopsis='Security update',
137
+ issue_date=timezone.now(),
138
+ )
139
+
140
+ def test_initial_counts_are_zero(self):
141
+ """Test that cached counts start at zero."""
142
+ self.assertEqual(self.erratum.cves_count, 0)
143
+ self.assertEqual(self.erratum.osreleases_count, 0)
144
+ self.assertEqual(self.erratum.affected_packages_count, 0)
145
+ self.assertEqual(self.erratum.fixed_packages_count, 0)
146
+ self.assertEqual(self.erratum.references_count, 0)
147
+
148
+ def test_cves_count_on_add(self):
149
+ """Test cves_count increments on add."""
150
+ cve1 = CVE.objects.create(cve_id='CVE-2024-0001')
151
+ cve2 = CVE.objects.create(cve_id='CVE-2024-0002')
152
+ self.erratum.cves.add(cve1)
153
+ self.erratum.refresh_from_db()
154
+ self.assertEqual(self.erratum.cves_count, 1)
155
+ self.erratum.cves.add(cve2)
156
+ self.erratum.refresh_from_db()
157
+ self.assertEqual(self.erratum.cves_count, 2)
158
+
159
+ def test_cves_count_on_remove(self):
160
+ """Test cves_count decrements on remove."""
161
+ cve = CVE.objects.create(cve_id='CVE-2024-0003')
162
+ self.erratum.cves.add(cve)
163
+ self.erratum.refresh_from_db()
164
+ self.assertEqual(self.erratum.cves_count, 1)
165
+ self.erratum.cves.remove(cve)
166
+ self.erratum.refresh_from_db()
167
+ self.assertEqual(self.erratum.cves_count, 0)
168
+
169
+ def test_cves_count_on_clear(self):
170
+ """Test cves_count resets to zero on clear."""
171
+ cve1 = CVE.objects.create(cve_id='CVE-2024-0004')
172
+ cve2 = CVE.objects.create(cve_id='CVE-2024-0005')
173
+ self.erratum.cves.add(cve1, cve2)
174
+ self.erratum.refresh_from_db()
175
+ self.assertEqual(self.erratum.cves_count, 2)
176
+ self.erratum.cves.clear()
177
+ self.erratum.refresh_from_db()
178
+ self.assertEqual(self.erratum.cves_count, 0)
179
+
180
+ def test_osreleases_count_on_add(self):
181
+ """Test osreleases_count increments on add."""
182
+ release = OSRelease.objects.create(name='Ubuntu 24.04')
183
+ self.erratum.osreleases.add(release)
184
+ self.erratum.refresh_from_db()
185
+ self.assertEqual(self.erratum.osreleases_count, 1)
186
+
187
+ def test_osreleases_count_on_remove(self):
188
+ """Test osreleases_count decrements on remove."""
189
+ release = OSRelease.objects.create(name='Ubuntu 24.04')
190
+ self.erratum.osreleases.add(release)
191
+ self.erratum.refresh_from_db()
192
+ self.assertEqual(self.erratum.osreleases_count, 1)
193
+ self.erratum.osreleases.remove(release)
194
+ self.erratum.refresh_from_db()
195
+ self.assertEqual(self.erratum.osreleases_count, 0)
196
+
197
+ def test_references_count_on_add(self):
198
+ """Test references_count increments on add."""
199
+ ref = Reference.objects.create(
200
+ ref_type='ADVISORY',
201
+ url='https://example.com/advisory/1',
202
+ )
203
+ self.erratum.references.add(ref)
204
+ self.erratum.refresh_from_db()
205
+ self.assertEqual(self.erratum.references_count, 1)
206
+
207
+ def test_references_count_on_remove(self):
208
+ """Test references_count decrements on remove."""
209
+ ref = Reference.objects.create(
210
+ ref_type='ADVISORY',
211
+ url='https://example.com/advisory/2',
212
+ )
213
+ self.erratum.references.add(ref)
214
+ self.erratum.refresh_from_db()
215
+ self.assertEqual(self.erratum.references_count, 1)
216
+ self.erratum.references.remove(ref)
217
+ self.erratum.refresh_from_db()
218
+ self.assertEqual(self.erratum.references_count, 0)
219
+
220
+ def test_str_uses_cached_counts(self):
221
+ """Test __str__ reflects cached count values."""
222
+ cve = CVE.objects.create(cve_id='CVE-2024-0010')
223
+ release = OSRelease.objects.create(name='RHEL 9')
224
+ self.erratum.cves.add(cve)
225
+ self.erratum.osreleases.add(release)
226
+ self.erratum.refresh_from_db()
227
+ result = str(self.erratum)
228
+ self.assertIn('1 related CVEs', result)
229
+ self.assertIn('affecting 1 OS Releases', result)
230
+ self.assertIn('providing 0 fixed Packages', result)
231
+
232
+ def test_counts_match_actual_m2m(self):
233
+ """Test cached counts stay in sync with actual M2M counts."""
234
+ cve1 = CVE.objects.create(cve_id='CVE-2024-0020')
235
+ cve2 = CVE.objects.create(cve_id='CVE-2024-0021')
236
+ release = OSRelease.objects.create(name='Debian 12')
237
+ self.erratum.cves.add(cve1, cve2)
238
+ self.erratum.osreleases.add(release)
239
+ self.erratum.refresh_from_db()
240
+ self.assertEqual(self.erratum.cves_count, self.erratum.cves.count())
241
+ self.assertEqual(self.erratum.osreleases_count, self.erratum.osreleases.count())
@@ -29,7 +29,7 @@ from util.filterspecs import Filter, FilterBar
29
29
 
30
30
  @login_required
31
31
  def erratum_list(request):
32
- errata = Erratum.objects.select_related()
32
+ errata = Erratum.objects.all()
33
33
 
34
34
  if 'e_type' in request.GET:
35
35
  errata = errata.filter(e_type=request.GET['e_type']).distinct()
@@ -19,3 +19,6 @@ from django.apps import AppConfig
19
19
 
20
20
  class HostsConfig(AppConfig):
21
21
  name = 'hosts'
22
+
23
+ def ready(self):
24
+ import hosts.signals # noqa
@@ -20,4 +20,4 @@ from django.db import models
20
20
 
21
21
  class HostManager(models.Manager):
22
22
  def get_queryset(self):
23
- return super().get_queryset().select_related()
23
+ return super().get_queryset().select_related('osvariant', 'arch', 'domain')
@@ -0,0 +1,17 @@
1
+ # Generated by Django 4.2.28 on 2026-02-11 04:55
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('hosts', '0009_host_errata'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name='hostrepo',
15
+ options={'ordering': ['host', 'repo']},
16
+ ),
17
+ ]
@@ -0,0 +1,33 @@
1
+ # Generated by Django 4.2.28 on 2026-02-11 04:56
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('hosts', '0010_alter_hostrepo_options'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='host',
15
+ name='bug_updates_count',
16
+ field=models.PositiveIntegerField(db_index=True, default=0),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='host',
20
+ name='errata_count',
21
+ field=models.PositiveIntegerField(db_index=True, default=0),
22
+ ),
23
+ migrations.AddField(
24
+ model_name='host',
25
+ name='packages_count',
26
+ field=models.PositiveIntegerField(db_index=True, default=0),
27
+ ),
28
+ migrations.AddField(
29
+ model_name='host',
30
+ name='sec_updates_count',
31
+ field=models.PositiveIntegerField(db_index=True, default=0),
32
+ ),
33
+ ]