It might help to consider what is what. I’ll try to explain things as I understand them, but anyone is very welcome to correct me where I’m wrong.
Meta class
When you define a class (e. g. Meta) inside of a class’ (e. g. ArticleEditForm) definition, the inner class is bound to the outer class itself. It’s similar to class attributes in the sense that upon generating instances, there is no new “copy” of the inner class being formed. So for example, if you do this…
f1 = MyForm()
f2 = MyForm()
… then f1.Meta and f2.Meta both refer to the same thing/class. This becomes clear if you do the following:
f1.Meta is f2.Meta # True
f1.Meta is MyForm.Meta
the point of the Meta class is to describe “meta characteristics” of the outer class (Form) itself and, by extension, all instances of the outer class (forms). The Meta class cannot be used for changing the behavior of individual instances. So for example:
f1.Meta.fields = ['bar', 'baz']
since f1.Meta refers to the same class as e. g. f2.Meta or MyForm.Meta, doing something like this affects the behavior/meta characteristics of MyForm itself and all current or future instances (forms).
Meta.fields
The Meta.fields attribute isn’t itself a collection of fields. It’s just meta data, saying which of the model’s (Article) fields that Form instances’ field collections (dictionaries, with field names as keys and Field instances as values) should be based on. It’s like part of a blueprint, a specification, for how individual forms’ collections of fields should be constructed. So trying to add an actual field (e. g. a forms.CharField instance) to this specification doesn’t make sense. This also follows from what Meta actually is, as described above. Speaking of which, based on what I’ve written so far you might want to ponder why it makes sense to force Meta.fields to be immutable (i. e. a tuple).
form_instance.fields
What you actually want is to include an additional field (a forms.CharField instance) for certain ArticleEditForm instances. If we look in Django’s code base, where the BaseForm class is defined, specifically its __init__ method, we can read this (around line 82):
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
self.fields = copy.deepcopy(self.base_fields)
This is more or less the answer to your question. You should be looking at modifying ArticleEditForm instances by modifying their .fields attribute.
Hopefully you can now see why a possible solution, or at least something that’s closer to what you want, might be:
class ArticleEditForm(forms.ModelForm):
"""
[descriptive docstring...]
"""
class Meta:
model = Article
fields = ('title','category','intro','content','image',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user.is_superuser:
self.fields['span'] = forms.CharField()
An alternative, possibly slightly more efficient (someone more knowledgeable please chime in on this) solution might be to create a ArticleSuperEditForm class that inherits from ArticleEditForm and has a modified Meta class, and then you put logic for deciding which kind of form to use in your views. I don’t know if this difference is large enough to be meaningful to the point where you would consider going with this less concise and less “fat model”-based solution, but I’m guessing no.
If you want to properly learn about OOP in Python, you might like this tutorial series by coreyms. I’ve only watched a couple of videos of it (having learned OOP from various other sources), but I’ve done the same author’s series on Django and it was very good.