'WirelessLANImportForm' has no field named 'scope'. #11365

Closed
opened 2025-12-29 21:44:14 +01:00 by adam · 11 comments
Owner

Originally created by @chucklesb on GitHub (Jul 9, 2025).

Originally assigned to: @jnovinger on GitHub.

Deployment Type

NetBox Cloud

NetBox Version

v4.3.3

Python Version

3.12

Steps to Reproduce

  1. Navigate to Wireless > Wireless LANs
  2. Click the "Import" button
  3. Click the "Upload File" tab
  4. Choose a CSV file and click the "Submit" button

Expected Behavior

The wireless LANs are imported into the database.

Observed Behavior

Server Error

There was a problem with your request. Please contact an administrator.


The complete exception is provided below:

<class 'ValueError'>

'WirelessLANImportForm' has no field named 'scope'.

Python version: 3.12.3
NetBox version: 4.3.3
Plugins: 
  nbc_auth_extensions: 1.19.1
  nbc_eventsink_extensions: 1.7.1
  nbc_requestmetric_extensions: 1.7.0
  netbox_branching: 0.6.0
  netbox_changes: 0.3.0
  netbox_labs_console: 1.7.2
Originally created by @chucklesb on GitHub (Jul 9, 2025). Originally assigned to: @jnovinger on GitHub. ### Deployment Type NetBox Cloud ### NetBox Version v4.3.3 ### Python Version 3.12 ### Steps to Reproduce 1. Navigate to Wireless > Wireless LANs 2. Click the "Import" button 3. Click the "Upload File" tab 4. Choose a CSV file and click the "Submit" button ### Expected Behavior The wireless LANs are imported into the database. ### Observed Behavior ## Server Error There was a problem with your request. Please contact an administrator. --- The complete exception is provided below: ``` <class 'ValueError'> 'WirelessLANImportForm' has no field named 'scope'. Python version: 3.12.3 NetBox version: 4.3.3 Plugins: nbc_auth_extensions: 1.19.1 nbc_eventsink_extensions: 1.7.1 nbc_requestmetric_extensions: 1.7.0 netbox_branching: 0.6.0 netbox_changes: 0.3.0 netbox_labs_console: 1.7.2 ```
adam added the type: bugstatus: acceptedseverity: low labels 2025-12-29 21:44:14 +01:00
adam closed this issue 2025-12-29 21:44:14 +01:00
Author
Owner

@chucklesb commented on GitHub (Jul 9, 2025):

I believe this error was caused by an invalid 'scope_id' value in my CSV. However, I think some improved input validation might be helpful. If I supply an invalid 'scope_id' when importing VLAN groups, I get a helpful error message: "Record 1 scope_id: Enter a whole number."

@chucklesb commented on GitHub (Jul 9, 2025): I believe this error was caused by an invalid 'scope_id' value in my CSV. However, I think some improved input validation might be helpful. If I supply an invalid 'scope_id' when importing VLAN groups, I get a helpful error message: "Record 1 scope_id: Enter a whole number."
Author
Owner

@arthanson commented on GitHub (Jul 10, 2025):

@chucklesb can you please provide the csv values you were using and the corresponding extra data you need to create first (i.e. VLAN groups?) Please provide enough information and data so someone can reproduce the issue you are seeing.

@arthanson commented on GitHub (Jul 10, 2025): @chucklesb can you please provide the csv values you were using and the corresponding extra data you need to create first (i.e. VLAN groups?) Please provide enough information and data so someone can reproduce the issue you are seeing.
Author
Owner

@chucklesb commented on GitHub (Jul 10, 2025):

Import a VLAN group with the following data:

name,slug,scope_type,scope_id
VLG0,vlg0,dcim.site,invalid

This results in a clear error message: Record 1 scope_id: Enter a whole number.

Image

Import a Wireless LAN with the following data:

ssid,status,scope_type,scope_id
WLAN0,active,dcim.site,invalid

This results in a Python ValueError: 'WirelessLANImportForm' has no field named 'scope'.

Image
@chucklesb commented on GitHub (Jul 10, 2025): Import a VLAN group with the following data: ``` name,slug,scope_type,scope_id VLG0,vlg0,dcim.site,invalid ``` This results in a clear error message: `Record 1 scope_id: Enter a whole number.` <img width="387" height="243" alt="Image" src="https://github.com/user-attachments/assets/22002aeb-6717-4b2b-8f52-dc5d9061eee0" /> --- Import a Wireless LAN with the following data: ``` ssid,status,scope_type,scope_id WLAN0,active,dcim.site,invalid ``` This results in a Python ValueError: `'WirelessLANImportForm' has no field named 'scope'.` <img width="955" height="576" alt="Image" src="https://github.com/user-attachments/assets/caafab0e-6723-4cbb-8278-056f35b6d217" />
Author
Owner

@stuntguy3000 commented on GitHub (Jul 14, 2025):

@arthanson Can you please assign this to me, I'm currently investigating this one - bit of an interesting one involving the CachedScopeMixin!

@stuntguy3000 commented on GitHub (Jul 14, 2025): @arthanson Can you please assign this to me, I'm currently investigating this one - bit of an interesting one involving the ``CachedScopeMixin``!
Author
Owner

@jnovinger commented on GitHub (Jul 14, 2025):

Thanks @stuntguy3000 , assigning to you.

@jnovinger commented on GitHub (Jul 14, 2025): Thanks @stuntguy3000 , assigning to you.
Author
Owner

@stuntguy3000 commented on GitHub (Jul 21, 2025):

Here we go, down the rabbit hole 🐰

I've spent way too long on this, and whilst I have a working "fix", I'm not entirely sure if it's suitable, or a band-aid warranting deeper investigation. If a maintainer wants to make a call, I hope this scratch notes help guide you. I am by no means a Django expert, and this is outside of my wheelhouse.

With that out of the way, here's my understanding of the issue, which may not be 100% accurate.


The Background to CachedScopeMixin Cleaning/Validation

This CachedScopeMixin Validation (clean function) was introduced in December '24 https://github.com/netbox-community/netbox/issues/18203 0cda10a204 to resolve first and foremost, a user experience issue. This is a fairly "interesting" way to fix what is simply a form validation issue; It's unclear if this is, or is not, the best approach (as the change impacts much more than form submissions - such as in this instance).

This specific function already caused one bug (#19588) before, so this makes the second one, afaik.

To prevent a stack trace...

Modify the validation error to not specify "scope" field, which may or may not exist depending on the context (form submission, API call or model insertion/bulk creation).

I've tested this approach for form submissions and the bulk creation originally reported, and this "resolves" the stack trace.

forms.py/CachedScopeMixin

    def clean(self):
        if self.scope_type and not (self.scope or self.scope_id):
            scope_type = self.scope_type.model_class()

            raise ValidationError(_("Please select a {scope_type}.").format(scope_type=scope_type._meta.model_name)) # Edited line to resolve this issue
        [...]

However...

Based on my commentary above, I'm not sure if this is suitable - as this seems to be fixing the error, not the bug. I could be mistaken, but I'd seriously challenge if this function really is the best approach for fixing the ORIGINAL issues mentioned previously, as it's behavior changes based on the user's context/actions.

It's also not well documented and missing a clear code flow/understanding of what the heck it's doing!


Clarifying the scope of the issue (impacted models)

Image

This issue pertains to Prefix, Cluster and WirelessLAN models, from my understanding. I've been able to replicate this issue on those object import forms, which do not contain a "Scope" field (which makes sense...)

Why don't other models (e.g. VLANGroup) have this issue?
I'm not sure.

Unlike the "impacted models", VLANGroup (as one example), does not inherit CachedScopeMixin - and does not undergo the same cleaning or validations, despite having identical Scope fields/features.

I cannot tell if this is an oversight, or intentional, and where I have to pass it on to those more in the know!


Thanks for coming to my Ted Talk.

Maintainers (@arthanson @jnovinger), I'm happy to submit my code above - but I'll let you decide on the next steps.

@stuntguy3000 commented on GitHub (Jul 21, 2025): _**Here we go, down the rabbit hole 🐰**_ I've spent way too long on this, and whilst I have a working "fix", I'm not entirely sure if it's suitable, or a band-aid warranting deeper investigation. If a maintainer wants to make a call, I hope this scratch notes help guide you. I am by no means a Django expert, and this is outside of my wheelhouse. With that out of the way, here's my understanding of the issue, which may not be 100% accurate. ------------ ### **The Background to CachedScopeMixin Cleaning/Validation** This CachedScopeMixin Validation (clean function) was introduced in December '24 https://github.com/netbox-community/netbox/issues/18203 https://github.com/netbox-community/netbox/commit/0cda10a204247993b39f50e5ec9564ec46c87c3d to resolve **first and foremost**, a user experience issue. This is a fairly "interesting" way to fix what is simply a form validation issue; It's unclear if this is, or is not, the best approach (as the change impacts **much more** than form submissions - such as in this instance). **This specific function** already caused one bug (#19588) before, **so this makes the second one**, afaik. ### **To prevent a stack trace...** Modify the validation error to not specify "scope" field, which may or may not exist **depending on the context** (form submission, API call or model insertion/bulk creation). I've tested this approach for form submissions and the bulk creation originally reported, and this "resolves" the stack trace. **forms.py/CachedScopeMixin** ```python def clean(self): if self.scope_type and not (self.scope or self.scope_id): scope_type = self.scope_type.model_class() raise ValidationError(_("Please select a {scope_type}.").format(scope_type=scope_type._meta.model_name)) # Edited line to resolve this issue [...] ``` # However... Based on my commentary above, I'm not sure if this is suitable - as this seems to be fixing the error, not the bug. I could be mistaken, but I'd seriously challenge if this function really is the best approach for fixing the ORIGINAL issues mentioned previously, as it's behavior **changes based on the user's context/actions**. _It's also not well documented and missing a clear code flow/understanding of what the heck it's doing!_ ------------ ### **Clarifying the scope of the issue (impacted models)** <img width="797" height="257" alt="Image" src="https://github.com/user-attachments/assets/a6487923-36ea-48dc-bd2a-601347a849b0" /> This issue pertains to `Prefix`, `Cluster` and `WirelessLAN` models, from my understanding. I've been able to replicate this issue on those object import forms, which do not contain a "Scope" field (which makes sense...) **Why don't other models (e.g. **VLANGroup**) have this issue?** I'm not sure. Unlike the "impacted models", `VLANGroup` (as one example), does not inherit `CachedScopeMixin` - and does not undergo the same cleaning or validations, despite having identical Scope fields/features. I cannot tell if this is an oversight, or intentional, and where I have to pass it on to those more in the know! ------------ Thanks for coming to my Ted Talk. Maintainers (@arthanson @jnovinger), I'm happy to submit my code above - but I'll let you decide on the next steps.
Author
Owner

@DanSheps commented on GitHub (Jul 24, 2025):

Just to chime in here...

Part of the issue is the validation for scope_id in the CachedScopeMixin is happening in the model and making an assumption the field is always going to be scope whereas this is form validation and most other forms validate using scope_id.

Complicating this is that edit forms will use scope = DynamicChoiceModelField whereas import forms will use just simply use scope_id. If you place a numeric you will get a different error then if you place a completely invalid text.

A way to fix this would perhaps be to have a key map in the form that before add_error is called, it rewrites any specific keys that don't exist if they exist in the key-map (example, scope -> scope_id), at least specifically for the import form. I would wait for Arthur or Jason to chime in but I see a bunch of possible solutions:

  1. Catch the validation error in Form.clean() for scope and re-throw it with a corrected key
  2. Short-circuit the check that would throw the error and throw it in form.clean() before super().clean() instead. IMO, lots of extra work here.
  3. Check for a field with the same name as the key but with _id appended and assume that that is the correct field; rename the field before add_error is called.
  4. Have a map of aliases -> field names for the specific form; rename the field before add_error is called
  5. Rename scope_id to scope
  6. Rename all forms with scope = to scope_id = then correct the clean()

I don't know what the correct solution is, but I don't think 5 or 6 are really sustainable

@DanSheps commented on GitHub (Jul 24, 2025): Just to chime in here... Part of the issue is the validation for scope_id in the CachedScopeMixin is happening in the model and making an assumption the field is always going to be `scope` whereas this is form validation and most other forms validate using scope_id. Complicating this is that edit forms will use `scope = DynamicChoiceModelField` whereas import forms will use just simply use scope_id. If you place a numeric you will get a different error then if you place a completely invalid text. A way to fix this would perhaps be to have a key map in the form that before add_error is called, it rewrites any specific keys that don't exist if they exist in the key-map (example, scope -> scope_id), at least specifically for the import form. I would wait for Arthur or Jason to chime in but I see a bunch of possible solutions: 1. Catch the validation error in Form.clean() for scope and re-throw it with a corrected key 2. Short-circuit the check that would throw the error and throw it in form.clean() before super().clean() instead. IMO, lots of extra work here. 3. Check for a field with the same name as the key but with `_id` appended and assume that that is the correct field; rename the field before add_error is called. 4. Have a map of aliases -> field names for the specific form; rename the field before add_error is called 5. Rename scope_id to scope 6. Rename all forms with `scope = ` to `scope_id = ` then correct the clean() I don't know what the correct solution is, but I don't think 5 or 6 are really sustainable
Author
Owner

@jnovinger commented on GitHub (Jul 30, 2025):

Apologies, @stuntguy3000 , this is on my radar to revisit, as soon as I'm clear from my current project.

@jnovinger commented on GitHub (Jul 30, 2025): Apologies, @stuntguy3000 , this is on my radar to revisit, as soon as I'm clear from my current project.
Author
Owner

@stuntguy3000 commented on GitHub (Jul 30, 2025):

Apologies, @stuntguy3000 , this is on my radar to revisit, as soon as I'm clear from my current project.

Not a worry! This is as about as of low priority as it gets. ☺️

@stuntguy3000 commented on GitHub (Jul 30, 2025): > Apologies, @stuntguy3000 , this is on my radar to revisit, as soon as I'm clear from my current project. Not a worry! This is as about as of low priority as it gets. ☺️
Author
Owner

@jnovinger commented on GitHub (Sep 2, 2025):

@stuntguy3000 and @DanSheps, my apologies for letting this sit for so long. That never should have happened.

I spent some time really digging in to understand this today. I tried a couple of Dan's ideas, but couldn't find anything that worked satisfactorily. What did work, was simply changing the validation error in CachedScopeMixin.clean() to be a general validation error (that is a non-field validation error), rather than about a specific field, e.g.

if self.scope_type and not (self.scope or self.scope_id):
    scope_type = self.scope_type.model_class()
    raise ValidationError(
        _("Please select a {scope_type}.").format(scope_type=scope_type._meta.model_name)
    )

From my testing, this has some nice properties:

  1. It maintains the validation at the model level, in case any forms (of whatever kind) or serializers don't do the validation.
  2. It makes the model-level validation non-field specific, meaning it doesn't cause follow-up exceptions in any forms when trying to add field-level validation errors for fields that do not exist on the form (e.g. this issue)
  3. It still allows for more specific field-level validation errors in the specific forms themselves. For instance, we should add scope_id validation in ScopedImportForm.clean() and for scope in ScopedForm.clean().
  4. It doesn't break the fixes for #18203 and #19520.

@stuntguy3000 , is this something you still want to run with? I'm more than happy to take it from this point, if you'd prefer.

Either way, I think the work for this bug should include the change to the model-level validation, the form- and field-specific additions above, and at least a regression test for this.

@jnovinger commented on GitHub (Sep 2, 2025): @stuntguy3000 and @DanSheps, my apologies for letting this sit for so long. That never should have happened. I spent some time really digging in to understand this today. I tried a couple of Dan's ideas, but couldn't find anything that worked satisfactorily. What did work, was simply changing the validation error in `CachedScopeMixin.clean()` to be a general validation error (that is a non-field validation error), rather than about a specific field, e.g. ```py if self.scope_type and not (self.scope or self.scope_id): scope_type = self.scope_type.model_class() raise ValidationError( _("Please select a {scope_type}.").format(scope_type=scope_type._meta.model_name) ) ``` From my testing, this has some nice properties: 1. It maintains the validation at the model level, in case any forms (of whatever kind) or serializers don't do the validation. 2. It makes the model-level validation non-field specific, meaning it doesn't cause follow-up exceptions in any forms when trying to add field-level validation errors for fields that do not exist on the form (e.g. this issue) 3. It still allows for more specific field-level validation errors in the specific forms themselves. For instance, we should add `scope_id` validation in `ScopedImportForm.clean()` and for `scope` in `ScopedForm.clean()`. 4. It doesn't break the fixes for #18203 and #19520. @stuntguy3000 , is this something you still want to run with? I'm more than happy to take it from this point, if you'd prefer. Either way, I think the work for this bug should include the change to the model-level validation, the form- and field-specific additions above, and at least a regression test for this.
Author
Owner

@stuntguy3000 commented on GitHub (Sep 3, 2025):

That's some fantastic analysis - you deserve to be the one who implements it!

@stuntguy3000 commented on GitHub (Sep 3, 2025): That's some fantastic analysis - you deserve to be the one who implements it!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#11365