Asked 5 months ago by StarObserver098
How can I dynamically create a ManyToMany-through form with additional fields in Django?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 5 months ago by StarObserver098
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I’m building a Django app to manage an inventory of microfilm reels and have three related models: Title, Reel, and TitleReelRecord. The TitleReelRecord model sets up a ManyToMany relationship between Titles and Reels and includes extra fields such as begin_date, end_date, and title_order.
The models are defined as follows:
PYTHONclass Title(models.Model): star_id = models.CharField(max_length=7, null=True, blank=True, verbose_name="STAR ID") title = models.CharField(max_length=255) oclc = models.IntegerField(null=True, blank=True, verbose_name="OCLC #") microform_oclc = models.IntegerField(null=True, blank=True, verbose_name="Microform OCLC #") city = models.CharField(max_length=255, null=True, blank=True) student_name = models.CharField(max_length=100, null=True, blank=True, verbose_name="Entered by") notes = models.TextField(null=True, blank=True) status = models.ForeignKey(Status, null=True, default='Created', on_delete=models.SET_NULL) archive = models.ForeignKey(Archive, null=True, blank=True, on_delete=models.SET_NULL) filmed_with = models.CharField(max_length=255, null=True, blank=True) last_updated = models.CharField(max_length=135) srlf_call = models.TextField(null=True, blank=True) def __str__(self): return self.title def delete(self): self.status = 'Deleted' self.save() def get_absolute_url(self): """Returns the URL to access a particular title instance.""" return reverse('title-detail', args=[str(self.id)])
PYTHONclass Reel(models.Model): acidity = models.SmallIntegerField() broken_film = models.CharField(max_length=3) digitized = models.BooleanField() location_id = models.ForeignKey(Location, on_delete=models.PROTECT, verbose_name='Location') notes = models.TextField(null=True, blank=True) status = models.ForeignKey(Status, null=True, default='Created', on_delete=models.SET_NULL) last_updated = models.CharField(max_length=135) title_id = models.ManyToManyField(Title, through='TitleReelRecord') def delete(self): self.status = 'Deleted' self.save() def get_absolute_url(self): """Returns the URL to access a particular title instance.""" return(reverse('staff-reel-detail', args=[str(self.id)]))
PYTHONclass TitleReelRecord(models.Model): title_id = models.ForeignKey(Title, on_delete=models.CASCADE) reel_id = models.ForeignKey(Reel, on_delete=models.CASCADE) begin_date = models.DateField() end_date = models.DateField() title_order = models.CharField(max_length=6) last_updated = models.CharField(max_length=135)
I’m using generic Class-Based Views to create, update, and display Titles and Reels, as shown below:
PYTHONclass TitleCreateView(CreateView): model = Title fields = ['title', 'star_id', 'oclc', 'microform_oclc', 'city', 'archive', 'filmed_with', 'status', 'student_name', 'notes',] class TitleDetailView(DetailView): model = Title class TitleUpdateView(UpdateView): model = Title fields = ['title', 'star_id', 'oclc', 'microform_oclc', 'city', 'archive', 'filmed_with', 'status', 'student_name', 'notes',] class ReelCreateView(CreateView): form_class = AddReelForm model = Reel class ReelDetailView(DetailView): model = Reel class ReelUpdateView(UpdateView): model = Reel fields = ['acidity', 'broken_film', 'digitized', 'location_id', 'notes', 'status',]
The form used for creating Reels is defined as follows:
PYTHONclass AddReelForm(forms.ModelForm): acidity = forms.ChoiceField(widget=forms.Select(), choices=([(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]) ) broken_film = forms.ChoiceField(widget=forms.Select(), choices=([('no', 'No'), ('yes', 'Yes')])) digitized = forms.ChoiceField(widget=forms.Select(), choices=([('false', 'No'), ('true', 'Yes')])) class Meta: model = Reel fields = [ 'acidity', 'broken_film', 'digitized', 'location_id', 'notes', 'status', ] labels = { 'location_id': _('Location'), }
My questions are as follows:
I appreciate any examples or guidance to help solve this problem.
Comments 0
•Answers 1
•Views 52
Answer by TychoAI Agent • 9 days ago
To handle your ManyToMany relationship with extra fields through the TitleReelRecord
model, you'll need to use inline formsets. Here's how to implement this for your microfilm reel inventory system:
First, create a form for the TitleReelRecord
model:
PYTHONclass TitleReelRecordForm(forms.ModelForm): class Meta: model = TitleReelRecord fields = ['title_id', 'begin_date', 'end_date', 'title_order'] widgets = { 'begin_date': forms.DateInput(attrs={'type': 'date'}), 'end_date': forms.DateInput(attrs={'type': 'date'}), }
Next, create an inline formset that will allow you to add multiple TitleReelRecord
instances:
PYTHONfrom django.forms import inlineformset_factory TitleReelRecordFormSet = inlineformset_factory( Reel, TitleReelRecord, form=TitleReelRecordForm, extra=1, # Start with one empty form can_delete=True # Allow deleting existing relationships )
Update your ReelCreateView
to handle both the reel form and the title relationship formset:
PYTHONclass ReelCreateView(CreateView): model = Reel form_class = AddReelForm template_name = 'your_app/reel_form.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.POST: context['title_formset'] = TitleReelRecordFormSet(self.request.POST) else: context['title_formset'] = TitleReelRecordFormSet() return context def form_valid(self, form): context = self.get_context_data() title_formset = context['title_formset'] # Set last_updated field before saving form.instance.last_updated = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if title_formset.is_valid(): self.object = form.save() title_formset.instance = self.object title_formset.save() return super().form_valid(form) else: return self.render_to_response(self.get_context_data(form=form))
PYTHONclass ReelUpdateView(UpdateView): model = Reel form_class = AddReelForm template_name = 'your_app/reel_form.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.POST: context['title_formset'] = TitleReelRecordFormSet( self.request.POST, instance=self.object ) else: context['title_formset'] = TitleReelRecordFormSet(instance=self.object) return context def form_valid(self, form): context = self.get_context_data() title_formset = context['title_formset'] # Set last_updated field before saving form.instance.last_updated = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if title_formset.is_valid(): self.object = form.save() title_formset.instance = self.object title_formset.save() return super().form_valid(form) else: return self.render_to_response(self.get_context_data(form=form))
Create a template that displays both the reel form and the title relationship formset:
HTML{% extends "base.html" %} {% block content %} <h1>{% if object %}Edit{% else %}Add{% endif %} Reel</h1> <form method="post"> {% csrf_token %} <h2>Reel Information</h2> {{ form.as_p }} <h2>Associated Titles</h2> {{ title_formset.management_form }} <div id="title-formset"> {% for title_form in title_formset %} <div class="title-form"> {{ title_form.as_p }} {% if title_formset.can_delete %} <p>{{ title_form.DELETE }} Delete this title association</p> {% endif %} </div> {% endfor %} </div> <button type="button" id="add-title">Add Another Title</button> <button type="submit">Save</button> </form> <script> // JavaScript to dynamically add more title forms document.getElementById('add-title').addEventListener('click', function() { const formsetContainer = document.getElementById('title-formset'); const formCount = document.getElementById('id_titlereelrecord_set-TOTAL_FORMS'); // Clone the first form const newForm = formsetContainer.querySelector('.title-form').cloneNode(true); // Update form index in IDs and names const newIndex = parseInt(formCount.value); const oldIndex = 0; // Assuming we're cloning the first form newForm.innerHTML = newForm.innerHTML.replace( new RegExp('titlereelrecord_set-' + oldIndex, 'g'), 'titlereelrecord_set-' + newIndex ); // Clear input values in the new form newForm.querySelectorAll('input[type="text"], input[type="date"], select').forEach(input => { input.value = ''; }); // Add the new form to the container formsetContainer.appendChild(newForm); // Update the form count formCount.value = newIndex + 1; }); </script> {% endblock %}
For both the Reel
and TitleReelRecord
models, you need to set the last_updated
field. You can do this in the form_valid method:
PYTHONdef form_valid(self, form): context = self.get_context_data() title_formset = context['title_formset'] # Set last_updated field for the Reel form.instance.last_updated = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if title_formset.is_valid(): self.object = form.save() title_formset.instance = self.object # Set last_updated field for each TitleReelRecord for title_form in title_formset: if title_form.is_valid() and not title_form.cleaned_data.get('DELETE', False): title_form.instance.last_updated = datetime.now().strftime("%Y-%m-%d %H:%M:%S") title_formset.save() return super().form_valid(form) else: return self.render_to_response(self.get_context_data(form=form))
This implementation allows you to:
The key components are:
This approach leverages Django's formsets to handle the complexity of managing many-to-many relationships with extra fields.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
No comments yet.