[feature] Add extraction metadata fields, state machine, and async extraction task#437
[feature] Add extraction metadata fields, state machine, and async extraction task#437atif09 wants to merge 5 commits into
Conversation
📝 WalkthroughWalkthroughThis PR introduces end-to-end firmware metadata extraction tracking. Sequence DiagramsequenceDiagram
participant Admin
participant FirmwareImageAdmin
participant extract_firmware_metadata
participant OpenWrtMetadataExtractor
participant Build
participant notify
Admin->>FirmwareImageAdmin: Upload/save FirmwareImage
FirmwareImageAdmin->>FirmwareImageAdmin: save_model() — reset extraction fields
FirmwareImageAdmin->>extract_firmware_metadata: transaction.on_commit → .delay(image_pk)
extract_firmware_metadata->>FirmwareImage: set STATUS_IN_PROGRESS
extract_firmware_metadata->>OpenWrtMetadataExtractor: .extract(image_file)
alt Extraction succeeds
OpenWrtMetadataExtractor-->>extract_firmware_metadata: metadata (board, target, fw_version…)
extract_firmware_metadata->>FirmwareImage: update fields, STATUS_SUCCESS
extract_firmware_metadata->>Build: _update_extraction_status()
Build-->>notify: _notify_extraction_complete() if status changes
else Extraction fails
OpenWrtMetadataExtractor-->>extract_firmware_metadata: UnsupportedImageError / DecompressionLimitExceeded / Timeout
extract_firmware_metadata->>FirmwareImage: STATUS_FAILED + failure_reason + extraction_log
extract_firmware_metadata->>notify: send error notification with admin URL
extract_firmware_metadata->>Build: _update_extraction_status()
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Incremental re-review of the changes since The new action is sound:
Previously reviewed findings on unchanged files are carried forward unchanged. Files Reviewed (2 files, incremental)
Previous Review Summaries (4 snapshots, latest commit ef60188)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit ef60188)Status: No Issues Found | Recommendation: Merge Full re-review performed after the branch history was force-pushed (the previous review commit The previously flagged critical issues remain resolved in the current code:
Additional checks performed on the new/changed production code:
Regression/behavior tests are present and aligned with the changes ( Files Reviewed (15 files)
Previous review (commit d9174a7)Status: No Issues Found | Recommendation: Merge The previously flagged CRITICAL issues have been resolved in the latest commits:
The new Files Reviewed (incremental: 6 files)
Previous review (commit 7e44f65)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
Files Reviewed (15 files)
Fix these issues in Kilo Cloud Previous review (commit 048b616)Status: No Issues Found | Recommendation: Merge Files Reviewed (9 files)
OverviewThis PR implements a comprehensive metadata extraction system for firmware images:
Code Quality Observations
The implementation follows Django and OpenWISP patterns and appears ready for merge. Reviewed by glm-5.2-20260616 · Input: 121K · Output: 3.3K · Cached: 173K |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 12
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@openwisp_firmware_upgrader/admin.py`:
- Around line 273-277: The code currently flips obj.extraction_status from
FirmwareImage.STATUS_SUCCESS to FirmwareImage.STATUS_MANUALLY_CONFIRMED whenever
obj.source == "dtb" regardless of what was edited; change this to only flip when
one of the DTB-editable metadata fields was actually changed by checking
form.changed_data. In the admin save/update path where obj and form are
available (the branch that checks obj.extraction_status ==
FirmwareImage.STATUS_SUCCESS and obj.source == "dtb"), compute the intersection
between form.changed_data and the set of DTB-editable field names (e.g., the
metadata fields you consider editable for DTB) and only set
obj.extraction_status = FirmwareImage.STATUS_MANUALLY_CONFIRMED if that
intersection is non-empty.
- Around line 255-263: The branch that handles file replacement (the block
starting with if change and "file" in form.changed_data) resets extracted
metadata but omits compat_version; update that block to also clear
obj.compat_version (set to the empty string or None consistent with the model)
so stale compatibility data isn't retained when a new file is uploaded; locate
the block that sets obj.extraction_status, obj.extraction_log,
obj.failure_reason, obj.board, obj.compatible, obj.target, obj.fw_version and
add clearing of obj.compat_version there.
- Around line 264-265: The task is enqueued immediately after super().save_model
which can run before the DB transaction commits; wrap the Celery enqueue in
django.db.transaction.on_commit so the task is scheduled only after commit. In
the save_model override (the method containing super().save_model(request, obj,
form, change)), replace the direct extract_firmware_metadata.delay(obj.pk) call
with transaction.on_commit(lambda: extract_firmware_metadata.delay(obj.pk)) and
ensure django.db.transaction is imported at the top of the file.
In `@openwisp_firmware_upgrader/base/models.py`:
- Around line 755-765: auto_create_device_firmwares currently enqueues
create_all_device_firmwares on every save when instance.extraction_status is
success/manually_confirmed; change it to only enqueue when the status
transitions into those states by reading the previous persisted status and
comparing it to the current one. In auto_create_device_firmwares, when created
is False, fetch the previous extraction_status (e.g. prev =
cls.objects.filter(pk=instance.pk).values_list("extraction_status",
flat=True).first()), then only call
transaction.on_commit(partial(create_all_device_firmwares.delay,
str(instance.pk))) if prev is not in (STATUS_SUCCESS, STATUS_MANUALLY_CONFIRMED)
and instance.extraction_status is in (STATUS_SUCCESS,
STATUS_MANUALLY_CONFIRMED); keep the early returns for created and for
non-confirmed current status.
- Around line 504-533: The current _validate_locked only checks the incoming
self.extraction_status which allows changing metadata when extraction_status is
moved into a locked state during the same save; modify _validate_locked to
always fetch the original row (keep the existing early return only for missing
pk), include "extraction_status" in the .values(...) call, and then proceed only
if either the original['extraction_status'] or self.extraction_status is in
self.LOCKED_STATUSES; if locked, compare the original metadata fields
("board","compatible","target","fw_version","compat_version","source") against
self and raise ValidationError on changes. Ensure you reference
_validate_locked, LOCKED_STATUSES and the original variable when implementing
this.
- Around line 418-430: The model fields extraction_log, failure_reason, board,
compatible, target, fw_version, compat_version, and source need translatable
verbose_name wrappers; update each field declaration (e.g., extraction_log =
models.TextField(...), failure_reason = models.CharField(...), board =
models.CharField(...), compatible = models.JSONField(...), target =
models.CharField(...), fw_version = models.CharField(...), compat_version =
models.CharField(...), source = models.CharField(...)) to pass a human-readable
first argument wrapped with gettext_lazy ( _("...") ) and ensure gettext_lazy is
imported as _ at the top of the file so Django i18n picks up labels in
admin/forms.
- Around line 337-350: The notification code hard-codes the admin reverse and
builds untranslated/raw-HTML messages; update _notify_extraction_complete in
AbstractFirmwareImage (and the similar notification in tasks.py) to reverse the
correct admin change URL using self._meta.app_label and self._meta.model_name
(build admin name dynamically), wrap HTML with format_html and
gettext/translation (e.g., use _()) to produce a translatable, escaped message,
and ensure any exception during reverse is logged/propagated instead of being
silently swallowed; modify AbstractDeviceFirmware.auto_create_device_firmwares
to only call create_all_device_firmwares on a transition into the confirmed
extraction_status (i.e., detect the status change on save to avoid recreating
OneToOne DeviceFirmware duplicates) and short-circuit if DeviceFirmware already
exists for the device; finally add verbose_name translations for metadata fields
on AbstractFirmwareImage (board, compatible, target, compat_version, source)
using gettext_lazy for user-facing labels.
In
`@openwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.py`:
- Around line 13-99: The migration adds defaults that leave existing
FirmwareImage rows with extraction_status="unconfirmed" but
trigger_metadata_extraction() is only invoked on post_save when created, so
existing rows will never be queued and will block Build.batch_upgrade() and
AbstractDeviceFirmware.clean(); fix by adding a RunPython data migration in
0018_build_status_firmwareimage_board_and_more.py that finds FirmwareImage
objects with extraction_status "unconfirmed" and either (a) calls
FirmwareImage.trigger_metadata_extraction() (or enqueues the
extract_firmware_metadata Celery task with the image PK) for each row, or (b) if
you can safely infer confirmation, set extraction_status to "success" or
"manually_confirmed" accordingly; ensure the migration imports FirmwareImage and
the task name extract_firmware_metadata and performs the enqueueing/updates
atomically and idempotently.
In `@openwisp_firmware_upgrader/tasks.py`:
- Around line 225-229: Mark the user-facing notification assigned to the message
variable as translatable and ensure HTML is safely rendered: import Django i18n
and HTML helpers (e.g. gettext/gettext_lazy as _ and format_html), replace the
plain f-string with a translatable string that uses named placeholders (e.g.
_('Metadata extraction failed for <a href="{admin_url}">{image}</a>: {reason}.
You can manually enter metadata or re-upload the image.')) and then call
format_html(...) or format_html_lazy(...) with admin_url=image and
reason=update.get("failure_reason", "unknown error") to safely interpolate
values; update the message assignment accordingly in
openwisp_firmware_upgrader/tasks.py.
- Around line 106-111: The _compat_blocks_pairing function incorrectly rejects
semantic versions with more than two components (e.g., "1.0.1"); update its
parsing so it only considers the major and minor components: split
compat_version on ".", take the first two pieces (treat missing minor as 0),
convert them to ints, then compare (major, minor) > (1, 0); keep the existing
exception handling for ValueError/AttributeError/TypeError to return False.
Ensure you update the logic inside _compat_blocks_pairing to handle strings,
ints, and malformed inputs robustly while preserving the intended comparison.
- Around line 216-218: Don't hard-code the admin route name; instead construct
the URL name from the actual Build model metadata so swapped/custom models work.
Retrieve the Build model (e.g. via image._meta.get_field('build').related_model
or apps.get_model if a string was used), then build the reverse name with
"%s_%s_change" % (build_model._meta.app_label, build_model._meta.model_name) and
call reverse("admin:%s" % that_name, args=[str(image.build_id)]). Replace the
literal "admin:firmware_upgrader_build_change" assignment to admin_url in
tasks.py (the line that sets admin_url using image.build_id) with this dynamic
construction. Ensure any exception handling still surfaces errors instead of
silently swallowing them.
In `@openwisp_firmware_upgrader/tests/test_models.py`:
- Around line 35-38: The class TestModels currently hardcodes app_label and
image_type which makes tests brittle; update TestModels to compute these from
the actual model metadata/constants instead of literals: remove the class
attributes app_label and image_type and set them by reading the target model's
meta and constants (e.g. use the model class referenced by TestUpgraderMixin or
FirmwareImage._meta.app_label and a canonical image type constant on the model
such as FirmwareImage.DEFAULT_IMAGE_TYPE or similar) so tests use
model._meta.app_label and the model's image-type constant at runtime.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9d26c19e-bee9-472e-87ce-8ce246abb75c
📒 Files selected for processing (12)
openwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/apps.pyopenwisp_firmware_upgrader/base/models.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_api.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/tests/test_models.pyopenwisp_firmware_upgrader/tests/test_tasks.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,html}
📄 CodeRabbit inference engine (Custom checks)
For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework
Files:
openwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_api.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/apps.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/base/models.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/tests/test_models.py
🧠 Learnings (4)
📚 Learning: 2026-02-27T19:08:56.218Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 383
File: openwisp_firmware_upgrader/tests/test_admin.py:50-50
Timestamp: 2026-02-27T19:08:56.218Z
Learning: In tests for openwisp-firmware-upgrader, derive app_label values from model meta instead of hard-coding. Specifically, let BaseTestAdmin compute app_label from Build._meta.app_label and config_app_label from Device._meta.app_label to ensure tests remain correct if models are swapped. This pattern should apply to all test files under openwisp_firmware_upgrader/tests.
Applied to files:
openwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_api.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/tests/test_models.py
📚 Learning: 2026-02-21T20:21:02.014Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: openwisp_firmware_upgrader/migrations/0015_add_group_to_batchupgradeoperation.py:16-26
Timestamp: 2026-02-21T20:21:02.014Z
Learning: In Django migrations within this repo, avoid adding explicit related_name to ForeignKey fields when the default reverse accessor (<model>_set) is acceptable. Rely on Django’s default naming convention to keep migrations concise and maintainable. Only specify related_name if you have a concrete, needed override for reverse relations.
Applied to files:
openwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py
📚 Learning: 2026-02-23T21:36:22.028Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: tests/openwisp2/sample_firmware_upgrader/migrations/0001_initial.py:61-67
Timestamp: 2026-02-23T21:36:22.028Z
Learning: In the sample_firmware_upgrader app within tests, avoid creating new migration files for small changes since the sample is for demonstration and testing. Review migrations with this context: the sample's initial migration may differ from the main app and need not be strictly aligned. If you change models for the demo, keep migrations minimal and document why divergence exists; don’t require parity with the main app migrations in PRs focused on the sample.
Applied to files:
tests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py
📚 Learning: 2026-03-07T01:16:15.471Z
Learnt from: atif09
Repo: openwisp/openwisp-firmware-upgrader PR: 387
File: openwisp_firmware_upgrader/admin.py:420-423
Timestamp: 2026-03-07T01:16:15.471Z
Learning: In openwisp-firmware-upgrader, UpgradeOperation and BatchUpgradeOperation always share the same organization (BatchUpgradeOperation.build.category.organization). A user who can view an UpgradeOperation can always view its related BatchUpgradeOperation. For the UpgradeOperationAdmin.change_view, model-level has_view_permission(request) suffices to show breadcrumbs for batch, and object-level permission checks are not required.
Applied to files:
openwisp_firmware_upgrader/admin.py
🔇 Additional comments (6)
openwisp_firmware_upgrader/apps.py (1)
32-33: LGTM!Also applies to: 58-63, 126-133
openwisp_firmware_upgrader/tests/test_models.py (1)
614-730: LGTM!openwisp_firmware_upgrader/tests/base.py (1)
80-81: LGTM!openwisp_firmware_upgrader/tests/test_api.py (1)
329-329: LGTM!Also applies to: 419-419, 1162-1162
openwisp_firmware_upgrader/tests/test_extractors.py (1)
7-8: LGTM!Also applies to: 77-78
openwisp_firmware_upgrader/tests/test_tasks.py (1)
1-260: LGTM!
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@openwisp_firmware_upgrader/tasks.py`:
- Line 142: Replace direct use of image.file.path (which fails for
non-filesystem storages) by streaming the file to a local temporary file and
passing that temp path to the extractor: in the extract_firmware_metadata flow
replace the call site where meta = extractor_class(image.file.path).extract()
with logic that opens image.file (or reads it) and writes its contents to a
tempfile.NamedTemporaryFile (or TemporaryDirectory) then call
extractor_class(temp_path).extract(); ensure the temp file is cleaned up after
extraction and preserve existing exception handling and status updates (use the
same extractor_class symbol and the same extraction logic, just feed it a local
filesystem path instead of image.file.path).
In `@openwisp_firmware_upgrader/tests/test_admin.py`:
- Around line 785-860: Extract a helper on the test class (e.g.,
_assert_readonly_fields_for_status) that uses self._create_firmware_image(),
sets fw.extraction_status and optionally fw.source, creates MockRequest with
User.objects.first(), instantiates FirmwareImageAdmin and calls
get_readonly_fields(request, obj=fw), then performs the membership assertions
for lists of fields (expected_in and expected_not_in) instead of repeating the
same setup in each test; replace bodies of test_firmware_image_readonly_fields_*
to call this helper with the appropriate status, source, and expected field
lists so all duplicated setup and loops are centralized.
In `@openwisp_firmware_upgrader/tests/test_tasks.py`:
- Around line 188-194: The test test_extract_firmware_metadata_image_not_found
currently checks mock_warning.call_args.args for the UUID which is brittle;
update the assertion to inspect the actual logged message(s) robustly by
converting the call arguments to strings and asserting that at least one
argument contains fake_pk (e.g., after calling
tasks.extract_firmware_metadata.run(fake_pk) use
mock_warning.assert_called_once() and then assert any(fake_pk in str(arg) for
arg in mock_warning.call_args_list[0].args) so the check works regardless of
logger formatting).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4ec40440-09d1-4d51-9ed7-2e5f1889abd1
📒 Files selected for processing (12)
openwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/apps.pyopenwisp_firmware_upgrader/base/models.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_api.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/tests/test_models.pyopenwisp_firmware_upgrader/tests/test_tasks.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,html}
📄 CodeRabbit inference engine (Custom checks)
For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework
Files:
openwisp_firmware_upgrader/tests/test_api.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_extractors.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/apps.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/tests/test_models.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/base/models.py
🧠 Learnings (4)
📚 Learning: 2026-02-27T19:08:56.218Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 383
File: openwisp_firmware_upgrader/tests/test_admin.py:50-50
Timestamp: 2026-02-27T19:08:56.218Z
Learning: In tests for openwisp-firmware-upgrader, derive app_label values from model meta instead of hard-coding. Specifically, let BaseTestAdmin compute app_label from Build._meta.app_label and config_app_label from Device._meta.app_label to ensure tests remain correct if models are swapped. This pattern should apply to all test files under openwisp_firmware_upgrader/tests.
Applied to files:
openwisp_firmware_upgrader/tests/test_api.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/tests/test_models.py
📚 Learning: 2026-02-21T20:21:02.014Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: openwisp_firmware_upgrader/migrations/0015_add_group_to_batchupgradeoperation.py:16-26
Timestamp: 2026-02-21T20:21:02.014Z
Learning: In Django migrations within this repo, avoid adding explicit related_name to ForeignKey fields when the default reverse accessor (<model>_set) is acceptable. Rely on Django’s default naming convention to keep migrations concise and maintainable. Only specify related_name if you have a concrete, needed override for reverse relations.
Applied to files:
tests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.py
📚 Learning: 2026-02-23T21:36:22.028Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: tests/openwisp2/sample_firmware_upgrader/migrations/0001_initial.py:61-67
Timestamp: 2026-02-23T21:36:22.028Z
Learning: In the sample_firmware_upgrader app within tests, avoid creating new migration files for small changes since the sample is for demonstration and testing. Review migrations with this context: the sample's initial migration may differ from the main app and need not be strictly aligned. If you change models for the demo, keep migrations minimal and document why divergence exists; don’t require parity with the main app migrations in PRs focused on the sample.
Applied to files:
tests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py
📚 Learning: 2026-03-07T01:16:15.471Z
Learnt from: atif09
Repo: openwisp/openwisp-firmware-upgrader PR: 387
File: openwisp_firmware_upgrader/admin.py:420-423
Timestamp: 2026-03-07T01:16:15.471Z
Learning: In openwisp-firmware-upgrader, UpgradeOperation and BatchUpgradeOperation always share the same organization (BatchUpgradeOperation.build.category.organization). A user who can view an UpgradeOperation can always view its related BatchUpgradeOperation. For the UpgradeOperationAdmin.change_view, model-level has_view_permission(request) suffices to show breadcrumbs for batch, and object-level permission checks are not required.
Applied to files:
openwisp_firmware_upgrader/admin.py
🔇 Additional comments (33)
openwisp_firmware_upgrader/apps.py (3)
32-32: LGTM!
58-63: LGTM!
126-133: LGTM!openwisp_firmware_upgrader/tests/test_models.py (2)
38-38: Avoid hard-codedimage_typetest fixture values.Please derive this from shared model/test constants to keep tests stable if model/image mappings are swapped.
Based on learnings: In tests for openwisp-firmware-upgrader, derive app_label values from model meta instead of hard-coding, and apply this pattern across
openwisp_firmware_upgrader/tests.
614-729: LGTM!openwisp_firmware_upgrader/tests/test_tasks.py (1)
1-1: LGTM!Also applies to: 10-10, 15-15, 18-19, 75-187, 195-260
openwisp_firmware_upgrader/tests/base.py (1)
80-81: LGTM!openwisp_firmware_upgrader/tests/test_api.py (1)
329-329: LGTM!Also applies to: 419-419, 1162-1162
openwisp_firmware_upgrader/tests/test_extractors.py (1)
7-8: LGTM!Also applies to: 77-78
openwisp_firmware_upgrader/base/models.py (5)
337-354: Use dynamic admin URL + translated safe notification message.This is still unresolved: Line 338 hard-codes the admin URL name, and Line 347-Line 350 builds a non-translated HTML message string.
418-430: Wrap new user-facing metadata labels in Django i18n.These new field labels remain non-translatable (for example, Line 418-Line 430), so admin/form labels won’t be localized.
As per coding guidelines: “For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework”.
504-533: Lock validation can be bypassed when status changes in the same save.This remains unresolved:
_validate_lockedgates only on currentself.extraction_status(Line 505), so metadata edits can slip through when transitioning out of a locked persisted status.
755-765: Only enqueue pairing on transition into confirmed states.Still unresolved: this hook schedules on every save while confirmed, instead of only when status transitions into success/manually_confirmed.
150-323: LGTM!Also applies to: 495-503, 629-638
openwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.py (1)
13-100: LGTM!tests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py (1)
13-100: LGTM!openwisp_firmware_upgrader/tests/test_admin.py (2)
23-23: LGTM!Also applies to: 551-552
862-921: LGTM!openwisp_firmware_upgrader/tasks.py (7)
106-111: Semantic version parsing still fails for versions with more than two components.The existing concern from previous review is still present: versions like
"1.0.1"will throwValueErrorduring tuple unpacking, causing the function to returnFalseand incorrectly allowing auto-pairing for versions that should be blocked.
216-229: Previously raised issues remain: hard-coded admin route and non-translatable message.Two concerns from prior review still apply:
Line 216-218: The admin URL uses a hard-coded route name
"admin:firmware_upgrader_build_change"which breaks for swapped/customized models.Line 225-229: The notification message is user-facing but not marked as translatable using Django i18n. As per coding guidelines for
**/*.{py,html}: user-facing strings must be marked translatable.
7-16: LGTM!
114-132: LGTM!
155-204: LGTM!
234-240: LGTM!
242-251: LGTM!openwisp_firmware_upgrader/admin.py (8)
255-265: Previous review issues remain unaddressed.Two issues from prior review are still present:
Missing
compat_versionclear (lines 259-263): When the file is replaced,compat_versionis not reset, leaving stale compatibility data that affects the pairing gate.Missing
transaction.on_commit(line 265): Callingextract_firmware_metadata.delay(obj.pk)immediately after save risks the Celery task running before the transaction commits, causing a race condition where the worker can't find the row.Suggested fix
+from django.db import transaction ... if change and "file" in form.changed_data: obj.extraction_status = FirmwareImage.STATUS_UNCONFIRMED obj.extraction_log = "" obj.failure_reason = "" obj.board = "" obj.compatible = [] obj.target = "" obj.fw_version = "" + obj.compat_version = "" obj.source = "" super().save_model(request, obj, form, change) - extract_firmware_metadata.delay(obj.pk) + transaction.on_commit( + lambda pk=obj.pk: extract_firmware_metadata.delay(pk) + ) return
273-277: DTB branch setsmanually_confirmedwithout checking which fields changed.This block runs for any edit on a successful DTB image, including changes to
buildortype, which shouldn't trigger the status flip. Gate on the editable metadata fields.Suggested fix
elif ( obj.extraction_status == FirmwareImage.STATUS_SUCCESS and obj.source == "dtb" + and any( + field in form.changed_data for field in ["target", "fw_version"] + ) ): obj.extraction_status = FirmwareImage.STATUS_MANUALLY_CONFIRMED
56-93: LGTM!
117-121: LGTM!
148-204: LGTM!
206-233: LGTM!
235-252: LGTM!
345-377: LGTM!
nemesifier
left a comment
There was a problem hiding this comment.
Thanks @atif09, this is a significant piece of work and the foundation looks solid. I see it is still marked as draft and depends on #421, so I will hold off on a detailed line-by-line review until it is ready.
A few high-level points to keep in mind while you finish it up:
- Sequencing with #421. This depends on the extractor foundation in #421. Let's agree on the overall scope first (ideally on #418), then decide whether to land the two together or behind a feature flag. I would rather review them as a coherent pair than piecemeal.
- Migrations. New model fields plus a state machine means migrations (and sample-app migrations) need to be correct and reversible. Make sure they are included and that the state-machine field has sensible defaults for existing rows.
- Async task safety. The async extraction task should be idempotent, bounded in resource use (it parses firmware binaries), and must fail into a clear error state rather than leaving objects stuck mid-state. Tie the states explicitly to the exception hierarchy from #421.
Ping me when it is out of draft and #421's scope is settled, and I will go through it properly.
There was a problem hiding this comment.
Pull request overview
Adds an asynchronous firmware-metadata extraction pipeline to OpenWISP Firmware Upgrader by introducing extraction metadata fields + state tracking on FirmwareImage, build-level aggregate extraction status on Build, and wiring Celery tasks + admin behaviors to automate extraction, notifications, and device pairing safeguards.
Changes:
- Added extraction-related model fields/statuses for
FirmwareImageand aggregateBuild.status, plus validation to block mass upgrades until metadata is confirmed. - Introduced
extract_firmware_metadataCelery task, post-save scheduling, build status recomputation, and notifications on completion/failure. - Updated admin to display extraction statuses/logs and enforce read-only metadata editing rules; expanded/adjusted test suite accordingly.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py | Test app migration adding build/image extraction fields for sample project. |
| openwisp_firmware_upgrader/tests/test_tasks.py | New tests for extraction task behavior, failure modes, and pairing gating. |
| openwisp_firmware_upgrader/tests/test_models.py | New tests for build status aggregation, locking rules, and upgrade blocking. |
| openwisp_firmware_upgrader/tests/test_extractors.py | Asserts OpenWrt upgrader is wired to OpenWrt metadata extractor. |
| openwisp_firmware_upgrader/tests/test_api.py | Updates query-count expectations impacted by new validation/status logic. |
| openwisp_firmware_upgrader/tests/test_admin.py | Adds admin tests for read-only behavior and save_model extraction/manual-confirm flows. |
| openwisp_firmware_upgrader/tests/base.py | Changes test helper default extraction status to simplify setup. |
| openwisp_firmware_upgrader/tasks.py | Adds _compat_blocks_pairing and extract_firmware_metadata task + failure notification logic. |
| openwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.py | Main app migration adding build/image extraction fields. |
| openwisp_firmware_upgrader/base/models.py | Adds build status tracking + notifications, image extraction fields + lock validation, and blocks upgrades for unconfirmed metadata. |
| openwisp_firmware_upgrader/apps.py | Connects metadata-extraction post_save signal and adds Firmware Images to the admin menu group. |
| openwisp_firmware_upgrader/admin.py | Introduces FirmwareImageAdmin, status badges/log display, and save_model behaviors for re-extraction/manual confirmation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
7743c6b to
048b616
Compare
1ea0b6d to
a0ed12b
Compare
CI Failures: Code Style and Test FailuresHello @atif09,
|
624a269 to
2845f49
Compare
{
"failures": [
{
"category": "Test Failure",
"description": "The CI environment encountered an `AttributeError` during the Django system checks, indicating a missing attribute in the URL configuration. This is likely due to a typo or an incorrect import in the URL routing setup.",
"details": "An `AttributeError: module 'openwisp2.sample_connection.api.views' has no attribute 'deviceconnection_detail_view'. Did you mean: 'deviceconnection_details_view'?` was raised. This points to an issue in the URL configuration, specifically in `tests/openwisp2/urls.py`, where `deviceconnection_detail_view` is referenced but likely misspelled or not correctly imported. The error message suggests `deviceconnection_details_view` as a possible correct attribute name.",
"fix": "Review the URL configuration in `tests/openwisp2/urls.py` and correct the reference to `deviceconnection_detail_view` to match the actual attribute name in `openwisp2.sample_connection.api.views` (likely `deviceconnection_details_view`)."
}
]
}Django URL Configuration ErrorHello @atif09,
Fix: Please correct the reference to |
…traction task #412 - Add extraction metadata fields to FirmwareImage (board, compatible, target, fw_version, compat_version, source, extraction_status, extraction_log, failure_reason) - Add state machine with statuses: unconfirmed, in_progress, success, failed, invalid, manually_confirmed - Add unique_together on (build, type) to prevent duplicate image uploads - Add _validate_locked() to make metadata fields read-only after confirmation, allowing empty fields to be filled - Add trigger_metadata_extraction post_save signal to schedule Celery task and set build status to analyzing - Add auto_create_device_firmware signal to trigger device pairing on confirmed status - Ad extract_firmware_metadata Celery task with handling for different exception cases - Add _compat_blocks_pairing() to skip device pairing when compat_version > 1.0 Related to #412
2845f49 to
9d343f4
Compare
|
Hi @atif09, Multiple Code Style Failures(Analysis for commit 2845f49) There are several code style issues detected in your commit:
Please address these issues and push a new commit. |
- Clear compat_version when the firmware file is replaced - Define Celery enqueue until after the admin save commits - Only mark DTB-backed images as manually confirmed when editable metadata changed - Add translatable verbose names for new metadata fields - Lock metadata based on persisted status not only the incoming one - Only enqueue device pairing on the transition into a confirmed state - Backfill legacy FirmwareImage extraction state before rollout - Removed hard-coded admin route namespace for swappable models - Mark notification text as translatable - Make the warning assertion robust to logger formatting style - Fix hardcoded admin reverse + missing i18n for extraction notifications and metadata field labels Related to #412
… locking #412 - track original extraction status on load so transition checks work correctly - fix new-object guard in _validate_locked to use _state.adding instead of pk check - lock metadata against bypass where status and fields change in the same save - skip device pairing re-trigger when image was already confirmed before the save - drop defensive getattr now that _original_extraction_status is always set in __init__ - add test to confirm metadata lock can't be bypassed by changing status in the same save - add test to confirm device pairing doesn't re-trigger on re-saves of confirmed images - add test to confirm compat_version is cleared when a firmware file is replaced - add test to confirm dtb status flip only triggers on target/fw_version changes, not other fields Related to #412
a2c9227 to
ce27733
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@openwisp_firmware_upgrader/base/models.py`:
- Around line 440-443: The `_original_extraction_status` attribute is only
initialized once in the `__init__` method, causing the
`auto_create_device_firmwares()` method to incorrectly treat repeated saves of
the same instance as new transitions if the extraction_status changes. After
each save cycle (in the save method or post_save signal handler referenced in
lines 777-790), refresh `_original_extraction_status` by setting it equal to the
current `extraction_status` value to ensure that subsequent saves don't trigger
duplicate pairing enqueues.
- Around line 319-326: The _update_extraction_status() method has a race
condition where the initial check against self.status can become stale across
concurrent extraction tasks, allowing multiple workers to send duplicate
completion notifications. Refactor the status update in the
Build.objects.filter() call to include the current old_status as part of the
filter condition, making the update atomic and ensuring only the worker that
successfully transitions from the old status to new status proceeds with the
notification logic. Use the return value from the update call to determine if
the transition succeeded before calling _notify_extraction_complete().
In `@openwisp_firmware_upgrader/tasks.py`:
- Around line 227-234: In the message template format string on lines 227-234,
there is a placeholder mismatch causing a silent failure. The template uses the
placeholder {admin_url} but the .format() method is given the argument
url=admin_url instead of admin_url=admin_url. Fix this by changing the keyword
argument in the .format() call from url=admin_url to admin_url=admin_url so that
the placeholder name matches the provided argument name.
In `@tests/openwisp2/sample_connection/api/views.py`:
- Line 17: The import statement for the base view class contains a typo in the
name DeviceConnenctionListCreateView where there is a double "nn" in
"Connenction". This will cause an ImportError since the actual class exported
from the upstream openwisp_controller.connection.api.views module is
DeviceConnectionListCreateView with a single "nn". Correct the typo by changing
DeviceConnenctionListCreateView to DeviceConnectionListCreateView in the import
statement.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6604dcbb-6616-49a1-a336-81c127427c2a
📒 Files selected for processing (16)
openwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/apps.pyopenwisp_firmware_upgrader/base/models.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/migrations/0019_backfill_extraction_status.pyopenwisp_firmware_upgrader/migrations/0020_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_api.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/tests/test_models.pyopenwisp_firmware_upgrader/tests/test_tasks.pytests/openwisp2/sample_connection/api/views.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pytests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{py,html}
📄 CodeRabbit inference engine (Custom checks)
For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework
Files:
tests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/migrations/0019_backfill_extraction_status.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/test_api.pytests/openwisp2/sample_connection/api/views.pyopenwisp_firmware_upgrader/migrations/0020_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/apps.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/tests/test_models.pyopenwisp_firmware_upgrader/base/models.py
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Place imports at the top of the file. Only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)
Avoid unnecessary blank lines inside function and method bodies
Mark user-facing strings for translation with Django i18n helpers in Django code
Write comments and docstrings only when they explain why code is shaped a certain way. Put comments before the relevant code block instead of scattering them inside it
Files:
tests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/migrations/0019_backfill_extraction_status.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/test_api.pytests/openwisp2/sample_connection/api/views.pyopenwisp_firmware_upgrader/migrations/0020_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/apps.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/tests/test_models.pyopenwisp_firmware_upgrader/base/models.py
🧠 Learnings (6)
📚 Learning: 2026-02-21T20:21:02.014Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: openwisp_firmware_upgrader/migrations/0015_add_group_to_batchupgradeoperation.py:16-26
Timestamp: 2026-02-21T20:21:02.014Z
Learning: In Django migrations within this repo, avoid adding explicit related_name to ForeignKey fields when the default reverse accessor (<model>_set) is acceptable. Rely on Django’s default naming convention to keep migrations concise and maintainable. Only specify related_name if you have a concrete, needed override for reverse relations.
Applied to files:
tests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/migrations/0019_backfill_extraction_status.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/migrations/0020_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.py
📚 Learning: 2026-02-23T21:36:22.028Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: tests/openwisp2/sample_firmware_upgrader/migrations/0001_initial.py:61-67
Timestamp: 2026-02-23T21:36:22.028Z
Learning: In the sample_firmware_upgrader app within tests, avoid creating new migration files for small changes since the sample is for demonstration and testing. Review migrations with this context: the sample's initial migration may differ from the main app and need not be strictly aligned. If you change models for the demo, keep migrations minimal and document why divergence exists; don’t require parity with the main app migrations in PRs focused on the sample.
Applied to files:
tests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py
📚 Learning: 2026-06-02T08:16:00.439Z
Learnt from: asmodehn
Repo: openwisp/openwisp-firmware-upgrader PR: 362
File: tests/openwisp2/urls.py:10-38
Timestamp: 2026-06-02T08:16:00.439Z
Learning: In this repository, treat `SAMPLE_APP` as a conventionally truthy environment variable: code should use the established pattern `os.environ.get("SAMPLE_APP", False)` and rely on the resulting value’s truthiness (truthy string → enabled). During code reviews, avoid recommending changes to explicit boolean parsing (e.g., checking for `
Applied to files:
tests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/migrations/0019_backfill_extraction_status.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/test_api.pytests/openwisp2/sample_connection/api/views.pyopenwisp_firmware_upgrader/migrations/0020_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/apps.pyopenwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/tests/test_models.pyopenwisp_firmware_upgrader/base/models.py
📚 Learning: 2026-06-15T20:19:28.326Z
Learnt from: atif09
Repo: openwisp/openwisp-firmware-upgrader PR: 437
File: openwisp_firmware_upgrader/tests/test_admin.py:1072-1147
Timestamp: 2026-06-15T20:19:28.326Z
Learning: In this codebase (OpenWISP projects, including openwisp-firmware-upgrader), prefer test methods that are fully self-contained: each test should do its own setup and make its own explicit assertions within the test body. Avoid introducing shared assertion helpers or parameterized helper methods that abstract away assertions across tests, even if it leads to some duplication.
Applied to files:
tests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_extractors.pytests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.pyopenwisp_firmware_upgrader/tests/test_api.pytests/openwisp2/sample_connection/api/views.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/tests/test_models.py
📚 Learning: 2026-02-27T19:08:56.218Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 383
File: openwisp_firmware_upgrader/tests/test_admin.py:50-50
Timestamp: 2026-02-27T19:08:56.218Z
Learning: In tests for openwisp-firmware-upgrader, derive app_label values from model meta instead of hard-coding. Specifically, let BaseTestAdmin compute app_label from Build._meta.app_label and config_app_label from Device._meta.app_label to ensure tests remain correct if models are swapped. This pattern should apply to all test files under openwisp_firmware_upgrader/tests.
Applied to files:
openwisp_firmware_upgrader/tests/base.pyopenwisp_firmware_upgrader/tests/test_extractors.pyopenwisp_firmware_upgrader/tests/test_api.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_tasks.pyopenwisp_firmware_upgrader/tests/test_models.py
📚 Learning: 2026-03-07T01:16:15.471Z
Learnt from: atif09
Repo: openwisp/openwisp-firmware-upgrader PR: 387
File: openwisp_firmware_upgrader/admin.py:420-423
Timestamp: 2026-03-07T01:16:15.471Z
Learning: In openwisp-firmware-upgrader, UpgradeOperation and BatchUpgradeOperation always share the same organization (BatchUpgradeOperation.build.category.organization). A user who can view an UpgradeOperation can always view its related BatchUpgradeOperation. For the UpgradeOperationAdmin.change_view, model-level has_view_permission(request) suffices to show breadcrumbs for batch, and object-level permission checks are not required.
Applied to files:
openwisp_firmware_upgrader/admin.py
🪛 ast-grep (0.43.0)
tests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.py
[info] 15-15: use help_text to document model columns
Context: models.CharField(blank=True, max_length=200, verbose_name="board")
Note: Security best practice.
(model-help-text)
[info] 20-22: use help_text to document model columns
Context: models.CharField(
blank=True, max_length=10, verbose_name="compat version"
)
Note: Security best practice.
(model-help-text)
[info] 37-48: use help_text to document model columns
Context: models.CharField(
blank=True,
choices=[
("unsupported_format", "Unsupported format"),
("out_of_memory", "Out of memory"),
("invalid_file", "Invalid file"),
("timeout", "Extraction timed out"),
],
default="",
max_length=20,
verbose_name="failure reason",
)
Note: Security best practice.
(model-help-text)
[info] 53-53: use help_text to document model columns
Context: models.CharField(blank=True, max_length=20, verbose_name="source")
Note: Security best practice.
(model-help-text)
[info] 58-58: use help_text to document model columns
Context: models.CharField(blank=True, max_length=100, verbose_name="target")
Note: Security best practice.
(model-help-text)
[warning] 15-15: always specify max_length for a Charfield
Context: models.CharField(blank=True, max_length=200, verbose_name="board")
Note: Security best practice.
(model-charfield-max-length)
[warning] 20-22: always specify max_length for a Charfield
Context: models.CharField(
blank=True, max_length=10, verbose_name="compat version"
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 37-48: always specify max_length for a Charfield
Context: models.CharField(
blank=True,
choices=[
("unsupported_format", "Unsupported format"),
("out_of_memory", "Out of memory"),
("invalid_file", "Invalid file"),
("timeout", "Extraction timed out"),
],
default="",
max_length=20,
verbose_name="failure reason",
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 53-53: always specify max_length for a Charfield
Context: models.CharField(blank=True, max_length=20, verbose_name="source")
Note: Security best practice.
(model-charfield-max-length)
[warning] 58-58: always specify max_length for a Charfield
Context: models.CharField(blank=True, max_length=100, verbose_name="target")
Note: Security best practice.
(model-charfield-max-length)
tests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py
[info] 15-27: use help_text to document model columns
Context: models.CharField(
choices=[
("analyzing", "Analyzing"),
("success", "Success"),
("failed", "Failed"),
("invalid", "Invalid"),
("manually_confirmed", "Manually Confirmed"),
],
db_index=True,
default="analyzing",
max_length=20,
verbose_name="extraction status",
)
Note: Security best practice.
(model-help-text)
[info] 32-32: use help_text to document model columns
Context: models.CharField(blank=True, max_length=200)
Note: Security best practice.
(model-help-text)
[info] 37-37: use help_text to document model columns
Context: models.CharField(blank=True, max_length=10)
Note: Security best practice.
(model-help-text)
[info] 52-65: use help_text to document model columns
Context: models.CharField(
choices=[
("unconfirmed", "Unconfirmed"),
("in_progress", "In Progress"),
("success", "Success"),
("failed", "Failed"),
("manually_confirmed", "Manually Confirmed"),
("invalid", "Invalid"),
],
db_index=True,
default="unconfirmed",
max_length=20,
verbose_name="extraction status",
)
Note: Security best practice.
(model-help-text)
[info] 70-80: use help_text to document model columns
Context: models.CharField(
blank=True,
choices=[
("unsupported_format", "Unsupported format"),
("out_of_memory", "Out of memory"),
("invalid_file", "Invalid file"),
("timeout", "Extraction timed out"),
],
default="",
max_length=20,
)
Note: Security best practice.
(model-help-text)
[info] 85-87: use help_text to document model columns
Context: models.CharField(
blank=True, max_length=50, verbose_name="firmware version"
)
Note: Security best practice.
(model-help-text)
[info] 92-92: use help_text to document model columns
Context: models.CharField(blank=True, max_length=20)
Note: Security best practice.
(model-help-text)
[info] 97-97: use help_text to document model columns
Context: models.CharField(blank=True, max_length=100)
Note: Security best practice.
(model-help-text)
[warning] 15-27: always specify max_length for a Charfield
Context: models.CharField(
choices=[
("analyzing", "Analyzing"),
("success", "Success"),
("failed", "Failed"),
("invalid", "Invalid"),
("manually_confirmed", "Manually Confirmed"),
],
db_index=True,
default="analyzing",
max_length=20,
verbose_name="extraction status",
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 52-65: always specify max_length for a Charfield
Context: models.CharField(
choices=[
("unconfirmed", "Unconfirmed"),
("in_progress", "In Progress"),
("success", "Success"),
("failed", "Failed"),
("manually_confirmed", "Manually Confirmed"),
("invalid", "Invalid"),
],
db_index=True,
default="unconfirmed",
max_length=20,
verbose_name="extraction status",
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 85-87: always specify max_length for a Charfield
Context: models.CharField(
blank=True, max_length=50, verbose_name="firmware version"
)
Note: Security best practice.
(model-charfield-max-length)
openwisp_firmware_upgrader/migrations/0020_alter_firmwareimage_board_and_more.py
[info] 15-15: use help_text to document model columns
Context: models.CharField(blank=True, max_length=200, verbose_name="board")
Note: Security best practice.
(model-help-text)
[info] 20-22: use help_text to document model columns
Context: models.CharField(
blank=True, max_length=10, verbose_name="compat version"
)
Note: Security best practice.
(model-help-text)
[info] 37-48: use help_text to document model columns
Context: models.CharField(
blank=True,
choices=[
("unsupported_format", "Unsupported format"),
("out_of_memory", "Out of memory"),
("invalid_file", "Invalid file"),
("timeout", "Extraction timed out"),
],
default="",
max_length=20,
verbose_name="failure reason",
)
Note: Security best practice.
(model-help-text)
[info] 53-53: use help_text to document model columns
Context: models.CharField(blank=True, max_length=20, verbose_name="source")
Note: Security best practice.
(model-help-text)
[info] 58-58: use help_text to document model columns
Context: models.CharField(blank=True, max_length=100, verbose_name="target")
Note: Security best practice.
(model-help-text)
[warning] 15-15: always specify max_length for a Charfield
Context: models.CharField(blank=True, max_length=200, verbose_name="board")
Note: Security best practice.
(model-charfield-max-length)
[warning] 20-22: always specify max_length for a Charfield
Context: models.CharField(
blank=True, max_length=10, verbose_name="compat version"
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 37-48: always specify max_length for a Charfield
Context: models.CharField(
blank=True,
choices=[
("unsupported_format", "Unsupported format"),
("out_of_memory", "Out of memory"),
("invalid_file", "Invalid file"),
("timeout", "Extraction timed out"),
],
default="",
max_length=20,
verbose_name="failure reason",
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 53-53: always specify max_length for a Charfield
Context: models.CharField(blank=True, max_length=20, verbose_name="source")
Note: Security best practice.
(model-charfield-max-length)
[warning] 58-58: always specify max_length for a Charfield
Context: models.CharField(blank=True, max_length=100, verbose_name="target")
Note: Security best practice.
(model-charfield-max-length)
openwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.py
[info] 15-27: use help_text to document model columns
Context: models.CharField(
choices=[
("analyzing", "Analyzing"),
("success", "Success"),
("failed", "Failed"),
("invalid", "Invalid"),
("manually_confirmed", "Manually Confirmed"),
],
db_index=True,
default="analyzing",
max_length=20,
verbose_name="extraction status",
)
Note: Security best practice.
(model-help-text)
[info] 32-32: use help_text to document model columns
Context: models.CharField(blank=True, max_length=200)
Note: Security best practice.
(model-help-text)
[info] 37-37: use help_text to document model columns
Context: models.CharField(blank=True, max_length=10)
Note: Security best practice.
(model-help-text)
[info] 52-65: use help_text to document model columns
Context: models.CharField(
choices=[
("unconfirmed", "Unconfirmed"),
("in_progress", "In Progress"),
("success", "Success"),
("failed", "Failed"),
("manually_confirmed", "Manually Confirmed"),
("invalid", "Invalid"),
],
db_index=True,
default="unconfirmed",
max_length=20,
verbose_name="extraction status",
)
Note: Security best practice.
(model-help-text)
[info] 70-80: use help_text to document model columns
Context: models.CharField(
blank=True,
choices=[
("unsupported_format", "Unsupported format"),
("out_of_memory", "Out of memory"),
("invalid_file", "Invalid file"),
("timeout", "Extraction timed out"),
],
default="",
max_length=20,
)
Note: Security best practice.
(model-help-text)
[info] 85-87: use help_text to document model columns
Context: models.CharField(
blank=True, max_length=50, verbose_name="firmware version"
)
Note: Security best practice.
(model-help-text)
[info] 92-92: use help_text to document model columns
Context: models.CharField(blank=True, max_length=20)
Note: Security best practice.
(model-help-text)
[info] 97-97: use help_text to document model columns
Context: models.CharField(blank=True, max_length=100)
Note: Security best practice.
(model-help-text)
[warning] 15-27: always specify max_length for a Charfield
Context: models.CharField(
choices=[
("analyzing", "Analyzing"),
("success", "Success"),
("failed", "Failed"),
("invalid", "Invalid"),
("manually_confirmed", "Manually Confirmed"),
],
db_index=True,
default="analyzing",
max_length=20,
verbose_name="extraction status",
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 52-65: always specify max_length for a Charfield
Context: models.CharField(
choices=[
("unconfirmed", "Unconfirmed"),
("in_progress", "In Progress"),
("success", "Success"),
("failed", "Failed"),
("manually_confirmed", "Manually Confirmed"),
("invalid", "Invalid"),
],
db_index=True,
default="unconfirmed",
max_length=20,
verbose_name="extraction status",
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 85-87: always specify max_length for a Charfield
Context: models.CharField(
blank=True, max_length=50, verbose_name="firmware version"
)
Note: Security best practice.
(model-charfield-max-length)
openwisp_firmware_upgrader/base/models.py
[info] 165-171: use help_text to document model columns
Context: models.CharField(
_("extraction status"),
max_length=20,
choices=BUILD_STATUS_CHOICES,
default=BUILD_STATUS_ANALYZING,
db_index=True,
)
Note: Security best practice.
(model-help-text)
[warning] 165-171: always specify max_length for a Charfield
Context: models.CharField(
_("extraction status"),
max_length=20,
choices=BUILD_STATUS_CHOICES,
default=BUILD_STATUS_ANALYZING,
db_index=True,
)
Note: Security best practice.
(model-charfield-max-length)
[info] 417-423: use help_text to document model columns
Context: models.CharField(
_("extraction status"),
max_length=20,
choices=EXTRACTION_STATUS_CHOICES,
default=STATUS_UNCONFIRMED,
db_index=True,
)
Note: Security best practice.
(model-help-text)
[info] 425-431: use help_text to document model columns
Context: models.CharField(
_("failure reason"),
max_length=20,
choices=FAILURE_REASON_CHOICES,
blank=True,
default="",
)
Note: Security best practice.
(model-help-text)
[info] 432-432: use help_text to document model columns
Context: models.CharField(_("board"), max_length=200, blank=True)
Note: Security best practice.
(model-help-text)
[info] 434-434: use help_text to document model columns
Context: models.CharField(_("target"), max_length=100, blank=True)
Note: Security best practice.
(model-help-text)
[info] 435-435: use help_text to document model columns
Context: models.CharField(_("firmware version"), max_length=50, blank=True)
Note: Security best practice.
(model-help-text)
[info] 436-436: use help_text to document model columns
Context: models.CharField(_("compat version"), max_length=10, blank=True)
Note: Security best practice.
(model-help-text)
[info] 437-437: use help_text to document model columns
Context: models.CharField(_("source"), max_length=20, blank=True)
Note: Security best practice.
(model-help-text)
[warning] 417-423: always specify max_length for a Charfield
Context: models.CharField(
_("extraction status"),
max_length=20,
choices=EXTRACTION_STATUS_CHOICES,
default=STATUS_UNCONFIRMED,
db_index=True,
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 425-431: always specify max_length for a Charfield
Context: models.CharField(
_("failure reason"),
max_length=20,
choices=FAILURE_REASON_CHOICES,
blank=True,
default="",
)
Note: Security best practice.
(model-charfield-max-length)
[warning] 432-432: always specify max_length for a Charfield
Context: models.CharField(_("board"), max_length=200, blank=True)
Note: Security best practice.
(model-charfield-max-length)
[warning] 434-434: always specify max_length for a Charfield
Context: models.CharField(_("target"), max_length=100, blank=True)
Note: Security best practice.
(model-charfield-max-length)
[warning] 435-435: always specify max_length for a Charfield
Context: models.CharField(_("firmware version"), max_length=50, blank=True)
Note: Security best practice.
(model-charfield-max-length)
[warning] 436-436: always specify max_length for a Charfield
Context: models.CharField(_("compat version"), max_length=10, blank=True)
Note: Security best practice.
(model-charfield-max-length)
[warning] 437-437: always specify max_length for a Charfield
Context: models.CharField(_("source"), max_length=20, blank=True)
Note: Security best practice.
(model-charfield-max-length)
🔇 Additional comments (37)
openwisp_firmware_upgrader/migrations/0018_build_status_firmwareimage_board_and_more.py (1)
3-100: LGTM!openwisp_firmware_upgrader/migrations/0019_backfill_extraction_status.py (1)
4-30: LGTM!openwisp_firmware_upgrader/migrations/0020_alter_firmwareimage_board_and_more.py (1)
3-61: LGTM!tests/openwisp2/sample_firmware_upgrader/migrations/0005_build_status_firmwareimage_board_and_more.py (1)
3-100: LGTM!tests/openwisp2/sample_firmware_upgrader/migrations/0006_alter_firmwareimage_board_and_more.py (1)
3-61: LGTM!openwisp_firmware_upgrader/tasks.py (4)
7-16: LGTM!
106-111: LGTM!
114-206: LGTM!
239-256: LGTM!openwisp_firmware_upgrader/tests/test_tasks.py (6)
1-18: LGTM!
32-78: LGTM!
80-130: LGTM!
132-191: LGTM!
193-222: LGTM!
224-264: LGTM!openwisp_firmware_upgrader/apps.py (1)
32-33: LGTM!Also applies to: 58-63, 126-133
openwisp_firmware_upgrader/tests/base.py (1)
80-81: LGTM!openwisp_firmware_upgrader/tests/test_models.py (1)
570-756: LGTM!Also applies to: 766-861, 979-1230, 1233-1393
openwisp_firmware_upgrader/tests/test_extractors.py (1)
1-93: LGTM!openwisp_firmware_upgrader/tests/test_api.py (1)
329-330: LGTM!Also applies to: 419-420, 1162-1163, 1312-1313, 1493-1494, 1532-1533
openwisp_firmware_upgrader/admin.py (9)
263-291: Past review comments have been addressed.The previous review raised three issues on this method:
- Missing
compat_versionreset on file change → now cleared at line 272- Task should use
transaction.on_commit→ now wrapped at line 275- DTB manual-confirm should gate on actual field changes → now checks
["target", "fw_version"]at lines 286-288All three are resolved.
65-103: LGTM!
126-130: LGTM!
157-214: LGTM!
215-242: LGTM!
244-262: LGTM!
358-390: LGTM!
569-596: LGTM!
961-972: LGTM!openwisp_firmware_upgrader/tests/test_admin.py (8)
125-128: LGTM!
217-253: LGTM!
596-606: LGTM!
835-857: LGTM!
859-1041: LGTM!
1042-1107: LGTM!
1109-1184: LGTM!
1186-1245: LGTM!
7e44f65 to
d9174a7
Compare
Test Failure in
|
- Make build status transition atomic to prevent duplicate notifications - Refresh _original_extraction_status in save() to prevent duplicate device pairing enqueue - Fix format string placeholder mismatch in failure notification using format_html - Fix the typo in the imported base view name Related to #412
d9174a7 to
ef60188
Compare
- Added re_extract_metadata bulk action to FirmwareImageAdmin that resets image metadata fields and reschedules extraction tasks - Added tests to cover single and multi-image bulk re-extraction Related to #413
8b6b7b6 to
57d4cfe
Compare
Black Formatting ErrorsHello @atif09, (Analysis for commit 8b6b7b6) It looks like there are some formatting issues in your code. The To fix this, please run the following command in your local environment: openwisp-qa-formatThis command will automatically format your code according to the project's style guidelines. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
openwisp_firmware_upgrader/tasks.py (1)
228-236: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueNotification shows raw
failure_reasonvalue instead of human-readable label.The notification message interpolates
update.get("failure_reason", "unknown error")which yields raw values like"timeout"or"out_of_memory". For consistency with_notify_extraction_completein models.py (which mapsBUILD_STATUS_CHOICESto display names), consider mappingfailure_reasonto its display name fromFAILURE_REASON_CHOICES.🔧 Suggested improvement
+ failure_reason_choices = dict(FirmwareImage.FAILURE_REASON_CHOICES) + reason_display = failure_reason_choices.get( + update.get("failure_reason", ""), + _("unknown error"), + ) notify.send( sender=image, type="generic_message", level="error", url=admin_url, target=image.build, message=format_html( _( 'Metadata extraction failed for <a href="{url}">{image}</a>: ' "{reason}. You can manually enter metadata or re-upload the image." ), url=admin_url, image=image, - reason=update.get("failure_reason", "unknown error"), + reason=reason_display, ), )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@openwisp_firmware_upgrader/tasks.py` around lines 228 - 236, The failure_reason value being interpolated in the format_html call is using raw string values instead of human-readable display labels. Replace the `update.get("failure_reason", "unknown error")` with a mapped value that looks up the display name from FAILURE_REASON_CHOICES, similar to how the _notify_extraction_complete method in models.py maps BUILD_STATUS_CHOICES to display names. This ensures the notification message shows user-friendly text like "Timeout" instead of the raw key "timeout".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@openwisp_firmware_upgrader/tasks.py`:
- Around line 228-236: The failure_reason value being interpolated in the
format_html call is using raw string values instead of human-readable display
labels. Replace the `update.get("failure_reason", "unknown error")` with a
mapped value that looks up the display name from FAILURE_REASON_CHOICES, similar
to how the _notify_extraction_complete method in models.py maps
BUILD_STATUS_CHOICES to display names. This ensures the notification message
shows user-friendly text like "Timeout" instead of the raw key "timeout".
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 18107094-8125-48c7-bb88-89f9d96035a3
📒 Files selected for processing (5)
openwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/base/models.pyopenwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/tests/test_models.py
💤 Files with no reviewable changes (1)
- openwisp_firmware_upgrader/tests/test_models.py
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
- GitHub Check: Python==3.12 | django~=4.2.0
- GitHub Check: Python==3.11 | django~=4.2.0
- GitHub Check: Python==3.10 | django~=4.2.0
- GitHub Check: Python==3.13 | django~=4.2.0
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{py,html}
📄 CodeRabbit inference engine (Custom checks)
For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework
Files:
openwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/base/models.py
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Place imports at the top of the file. Only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)
Avoid unnecessary blank lines inside function and method bodies
Mark user-facing strings for translation with Django i18n helpers in Django code
Write comments and docstrings only when they explain why code is shaped a certain way. Put comments before the relevant code block instead of scattering them inside it
Files:
openwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/base/models.py
🧠 Learnings (4)
📚 Learning: 2026-06-02T08:16:00.439Z
Learnt from: asmodehn
Repo: openwisp/openwisp-firmware-upgrader PR: 362
File: tests/openwisp2/urls.py:10-38
Timestamp: 2026-06-02T08:16:00.439Z
Learning: In this repository, treat `SAMPLE_APP` as a conventionally truthy environment variable: code should use the established pattern `os.environ.get("SAMPLE_APP", False)` and rely on the resulting value’s truthiness (truthy string → enabled). During code reviews, avoid recommending changes to explicit boolean parsing (e.g., checking for `
Applied to files:
openwisp_firmware_upgrader/tasks.pyopenwisp_firmware_upgrader/admin.pyopenwisp_firmware_upgrader/tests/test_admin.pyopenwisp_firmware_upgrader/base/models.py
📚 Learning: 2026-03-07T01:16:15.471Z
Learnt from: atif09
Repo: openwisp/openwisp-firmware-upgrader PR: 387
File: openwisp_firmware_upgrader/admin.py:420-423
Timestamp: 2026-03-07T01:16:15.471Z
Learning: In openwisp-firmware-upgrader, UpgradeOperation and BatchUpgradeOperation always share the same organization (BatchUpgradeOperation.build.category.organization). A user who can view an UpgradeOperation can always view its related BatchUpgradeOperation. For the UpgradeOperationAdmin.change_view, model-level has_view_permission(request) suffices to show breadcrumbs for batch, and object-level permission checks are not required.
Applied to files:
openwisp_firmware_upgrader/admin.py
📚 Learning: 2026-02-27T19:08:56.218Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 383
File: openwisp_firmware_upgrader/tests/test_admin.py:50-50
Timestamp: 2026-02-27T19:08:56.218Z
Learning: In tests for openwisp-firmware-upgrader, derive app_label values from model meta instead of hard-coding. Specifically, let BaseTestAdmin compute app_label from Build._meta.app_label and config_app_label from Device._meta.app_label to ensure tests remain correct if models are swapped. This pattern should apply to all test files under openwisp_firmware_upgrader/tests.
Applied to files:
openwisp_firmware_upgrader/tests/test_admin.py
📚 Learning: 2026-06-15T20:19:28.326Z
Learnt from: atif09
Repo: openwisp/openwisp-firmware-upgrader PR: 437
File: openwisp_firmware_upgrader/tests/test_admin.py:1072-1147
Timestamp: 2026-06-15T20:19:28.326Z
Learning: In this codebase (OpenWISP projects, including openwisp-firmware-upgrader), prefer test methods that are fully self-contained: each test should do its own setup and make its own explicit assertions within the test body. Avoid introducing shared assertion helpers or parameterized helper methods that abstract away assertions across tests, even if it leads to some duplication.
Applied to files:
openwisp_firmware_upgrader/tests/test_admin.py
🔇 Additional comments (24)
openwisp_firmware_upgrader/base/models.py (8)
152-173: LGTM!
214-227: LGTM!
299-331: LGTM!
333-367: LGTM!
395-453: LGTM!
471-473: LGTM!
516-524: LGTM!Also applies to: 526-561
787-800: LGTM!openwisp_firmware_upgrader/tasks.py (3)
8-8: LGTM!
116-206: LGTM!
241-258: LGTM!openwisp_firmware_upgrader/admin.py (8)
130-131: LGTM!
159-178: LGTM!
180-215: LGTM!
217-244: LGTM!
246-263: LGTM!
265-293: LGTM!
295-323: LGTM!
416-422: LGTM!openwisp_firmware_upgrader/tests/test_admin.py (5)
217-253: LGTM!
254-329: LGTM!
666-666: LGTM!
1186-1262: LGTM!
1263-1322: LGTM!
Related to #412 & #413
Checklist
Reference to Existing Issue
Closes #412 & #413
Description of Changes
Adds extraction metadata fields and a state machine to FirmwareImage to track async metadata extraction. A Celery task extracts firmware metadata (board, target, compatible, version, source) after upload, with a locked state preventing edits after confirmation. Prevents duplicate image uploads via a unique_together constraint and skips device firmware pairing when compat_version exceeds 1.0
NOTE: These changes focus on the async Celery task, state machine wiring, fwtool/DTB metadata extraction, and admin notifications. UI improvements (inline display of extraction status and metadata fields in the build admin etc) will be covered in #414 after discussing more with the mentors.