class QuestionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.extra_attributes = Attribute.objects.all()
question_attribute_values = {}
if self.instance.pk:
question_attribute_values = {
attr.attribute.id : attr.value
for attr in QuestionAttribute.objects.filter(question=self.instance)
}
for attribute in self.extra_attributes:
self.fields[f"attribute_{attribute.id}"] = forms.CharField(
label=attribute.name,
required=False,
initial=question_attribute_values.get(attribute.id, '')
)
def save(self, commit=True):
question = super().save(commit=commit)
for attribute in self.extra_attributes:
value = self.cleaned_data[f"attribute_{attribute.id}"]
QuestionAttribute.objects.update_or_create(
question=question,
attribute=attribute,
defaults={"value": value}
)
return question
class Meta:
model = Question
exclude = ('deleted_at', 'restored_at', 'transaction_id')
Welcome @amosanurag !
It would help if you posted the models and the template where you are rendering the form.
Also, are you getting any error messages in the console with this? Or are these fields simply not being displayed on the page?
Hello @KenWhitesell,
First off, I just wanted to say I’m a big fan and have been following your responses on the Django forum for quite some time—they’ve been incredibly helpful!
Context:
I’m building a custom form for the Question
model in the QuestionAdmin
class. The goal is to allow dynamic addition of fields based on attributes associated with the Question
model. Here’s the setup:
- I added a Many-to-Many relationship between the
Question
model and anAttribute
model via an intermediateQuestionAttribute
model. - The
QuestionAttribute
model includes the fields:question_id
,attribute_id
, and avalue
(aCharField
). - Conventionally, these additional fields could be displayed using inlines, but I want to render them as regular fields directly in the admin form, prefilled with values (if any).
Problem:
When customizing the form in QuestionAdmin
, only the fields from the Question
model are rendered. The dynamically added fields from the Attribute
model (via QuestionAttribute
) are not being displayed.
Code Overview:
Here’s a brief summary of the models for context:
# The QuestionAdmin
class QuestionAdmin(admin.ModelAdmin):
list_display = ('id', 'question_text', 'answer_text', 'image_id', 'image_exists_in_gcp', 'view_link')
list_filter = ('level_tags','topic', 'image_exists_in_gcp')
form = QuestionForm
readonly_fields = ['image_exists_in_gcp']
# The models :
class Question(SoftDeleteModel):
question_text = models.TextField()
image = models.ImageField(upload_to="question_images/", blank=True, null=True)
image_id = models.CharField(max_length=100,blank=True, null=True)
image_exists_in_gcp = models.BooleanField(default=False, verbose_name="Does image exist in GCP")
answer_text = models.TextField()
additional_image=models.CharField(max_length=100,blank=True, null=True)
image_request=models.TextField(blank=True, null=True)
visible_extra_info=models.TextField(blank=True, null=True)
hidden_extra=models.CharField(max_length=255,blank=True, null=True)
level_tags=models.CharField(max_length=100,blank=True, null=True)
subject_tags=models.CharField(max_length=100,blank=True, null=True)
keywords=models.CharField(max_length=100,blank=True, null=True)
card_family=models.CharField(max_length=100,blank=True, null=True)
needs_a_mirrored_card=models.CharField(max_length=100,blank=True, null=True)
additional_chapters=models.CharField(max_length=100,blank=True, null=True)
directionality=models.CharField(max_length=100,blank=True, null=True)
created_map=models.CharField(max_length=255,blank=True, null=True)
notes=models.CharField(max_length=255,blank=True, null=True)
ap_terms=models.CharField(max_length=100,blank=True, null=True)
texas_standards=models.CharField(max_length=100,blank=True, null=True)
va_standards=models.CharField(max_length=100,blank=True, null=True)
ny_standards=models.CharField(max_length=100,blank=True, null=True)
former_images=models.CharField(max_length=100,blank=True, null=True)
topic=models.CharField(max_length=255,blank=True, null=True)
chapters=models.ManyToManyField(Chapter, related_name="questions")
extras = models.ManyToManyField("Attribute", through="QuestionAttribute", related_name="questions")
class Meta:
db_table = "questions"
def __str__(self):
return self.question_text # Return first 50 characters of the question text
class Attribute(models.Model):
name = models.CharField(max_length=150, unique=True)
def clean(self):
# Ensure the attribute name does not conflict with any Question model field names, foreign keys, or many-to-many fields
prohibited_names = [field.name for field in Question._meta.get_fields()]
if self.name in prohibited_names:
raise ValidationError(f"Attribute name '{self.name}' conflicts with an existing field name.")
def __str__(self):
return self.name
class QuestionAttribute(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
attribute = models.ForeignKey(Attribute, on_delete=models.CASCADE)
value = models.CharField(max_length=255, null=True)
class Meta:
unique_together = ('question', 'attribute')
def __str__(self):
return f"{self.question} - {self.attribute}: {self.value}"
I don’t have a definitive answer for this, but I’d guess you’d need to override the get_form
method on the ModelAdmin
class in addition to (possibly) needing to override the fields
attribute. You’ll want to read the source code for that class to get a feel of what needs to be changed.
Fundamentally however, I think this is the wrong solution.
If you need to do this because this is an “end-user-facing page”, then you probably shouldn’t be using the admin for this.
Quoting from the docs at The Django admin site | Django documentation | Django
If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views.
I’ve seen it happen many times. You could easily spend much more time trying to make the admin work the way you want it to than you would by making a custom view.