how can i get all attribute data from ManyToMany field Django? - django

i want to get all attribute data of articles in SetRundown forms like title, category, author via ManyToManyfield i want to show all article in Rundown form with title category and author name can anybody know how can i do this...? if i run my code with {% render_field form.articles %} then it will show the all articles title but i want the category and author name too with titles....
models.py
class Article(models.Model):
title = models.CharField(max_length=300, help_text="Short title")
category = models.ForeignKey(Category, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
class SetRundown(models.Model):
pool_title = models.CharField(max_length=200)
articles = models.ManyToManyField(Article)
forms.py
from django import forms
class SetRundownForm(forms.ModelForm):
class Meta:
model = SetRundown
fields = ['pool_title', 'time_pool', 'articles']
def __init__(self, *args, **kwargs):
super(SetRundownForm, self).__init__(*args, **kwargs)
self.fields['articles'].queryset = Article.objects.filter(story_status='fr')
create_form.html
<form method="POST">{% csrf_token %}
{% render_field form.pool_title type="text" %}
{% render_field form.time_pool type="menu" %}
{% for article in form.articles %}
{{ article.title }}
{{ article.category }}
{{ article.author.username }}
{% endfor %}
<button class="btn btn-secondary" type="submit">Submit</button>
</form>
{% endblock %}

You can access the .queryset of the .field:
{% for article in form.articles.field.queryset %}
{{ article.title }}
{{ article.category }}
{{ article.author.username }}
{% endfor %}
In your SetRundownForm, you can make use of .select_related(…) [Django-doc] to avoid an N+1 problem:
class SetRundownForm(forms.ModelForm):
class Meta:
model = SetRundown
fields = ['pool_title', 'time_pool', 'articles']
def __init__(self, *args, **kwargs):
super(SetRundownForm, self).__init__(*args, **kwargs)
self.fields['articles'].queryset = Article.objects.select_related(
'author'
).filter(story_status='fr')

Related

Customizing inlineformset choice in Django template

I've got some forms I'm trying to customize.
I render the fields manually - and it all works fine until get to a particular field (which is an InlineFormset itself). I'm trying to customize those options but can't seem to figure out how to do so.
my forms.py looks like this:
class SummativeScoreForm(forms.ModelForm):
subdomain_proficiency_level = forms.ModelChoiceField(
empty_label="Undecided",
queryset=SubdomainProficiencyLevel.objects.none(),
widget=forms.RadioSelect,
required=False,
)
def __init__(self, request, *args, **kwargs):
super(SummativeScoreForm, self).__init__(*args, **kwargs)
if self.instance:
if request.user == self.instance.summative.employee:
self.fields["subdomain_proficiency_level"].disabled = True
self.fields[
"subdomain_proficiency_level"
].queryset = SubdomainProficiencyLevel.objects.filter(
subdomain=self.instance.subdomain
)
self.fields[
"subdomain_proficiency_level"
].label = f"""
{self.instance.subdomain.character_code}:
{self.instance.subdomain.short_description}
"""
class Meta:
model = SummativeScore
fields = "__all__"
SummativeScoreInlineFormset = inlineformset_factory(
Summative,
SummativeScore,
fields=("subdomain_proficiency_level",),
can_delete=False,
extra=0,
form=SummativeScoreForm,
)
My template for summative_score_form looks like this:
<form method="post" novalidate>
{% csrf_token %}
{% include "myapp/includes/summative_score_response_formset_snippet.html" with formset=form %}
<button type="submit" class="btn btn-primary"><i class="fal fa-clipboard-check"></i> Submit Updated Scores</button>
</form>
The summative_score_response_formset_snippet looks like this:
{{ formset.management_form }}
{% for formset_form in formset.forms %}
{% if formset_form.non_field_errors %}
<ul>
{% for error in formset_form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% for hidden_field in formset_form.hidden_fields %}
{% if hidden_field.errors %}
<ul>
{% for error in hidden_field.errors %}
<li>
(Hidden field {{ hidden_field.name }}) {{ error }}
</li>
{% endfor %}
</ul>
{% endif %}
{{ hidden_field }}
{% endfor %}
{% for field in formset_form.visible_fields %}
{% if field.name == 'subdomain_proficiency_level' %}
<label class="form-check-label" for="{{ field.id_for_label }}">
{{ field.label }}
</label>
<ul id="{{ field.auto_id }}" class="form-check mt-2">
{% for choice in formset_form.subdomain_proficiency_level %}
<div class="form-check">
<!--
THIS IS THE PART I WOULD LIKE TO CUSTOMIZE:
Unsatisfactory (name) Lorum Ipsum (description)
Satisfactory (name) Lorum Ipsum (description)
Excellent (name) Lorum Ipsum (description)
CURRENTLY IT ONLY SHOWS THE NAME
-->
{{ choice }}
</div>
{% endfor %}
</ul>
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
{% else %}
{{ field }}
{% endif %}
{% endfor %}
{% endfor %}
My models look like this:
class SubdomainProficiencyLevel(CreateUpdateMixin):
"THIS IS THE 'UNSATISFACTORY' (name) 'LORUM IPSUM' (description)"
name = models.CharField(max_length=75)
description = models.TextField()
sequence = models.IntegerField()
class Meta:
ordering = ["sequence"]
verbose_name = "Subdomain Rank"
verbose_name_plural = "Subdomain Ranks"
def __str__(self):
"""
THIS IS WHAT IS 'CHOICE' IN THE FORM
I'm trying to edit this to add styles to the self.description on the form
"""
return f"{self.name}"
class SummativeScore(CreateUpdateMixin, CreateUpdateUserMixin):
summative = models.ForeignKey(Summative, on_delete=models.PROTECT)
subdomain = models.ForeignKey(Subdomain, on_delete=models.PROTECT)
subdomain_proficiency_level = models.ForeignKey(
SubdomainProficiencyLevel,
on_delete=models.PROTECT,
null=True,
blank=True,
)
class Meta:
ordering = ["subdomain__character_code"]
verbose_name = "SummativeScore"
verbose_name_plural = "SummativeScores"
def __str__(self):
"""Unicode representation of SummativeScore."""
return f"{self.subdomain_proficiency_level}"
The view is a Class Based FormView
class SummativeScoreFormView(
LoginRequiredMixin,
UserIsObserverOrObserveeMixin,
SingleObjectMixin,
FormView,
):
model = Summative
template_name = "myapp/summative_score_form.html"
pk_url_kwarg = "summative_id"
def get(self, request, *args, **kwargs):
summative_id = kwargs.pop("summative_id")
self.object = self.get_object(
queryset=Summative.objects.filter(id=summative_id)
)
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
summative_id = kwargs.pop("summative_id")
self.object = self.get_object(
queryset=Summative.objects.filter(id=summative_id)
)
return super().post(request, *args, **kwargs)
def get_form(self, form_class=None):
formset = SummativeScoreInlineFormset(
**self.get_form_kwargs(), instance=self.object
)
return formset
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["form_kwargs"] = {"request": self.request}
return kwargs
def form_valid(self, form):
form.save()
messages.success(self.request, "Changes were saved!")
return super().form_valid(form)
def form_invalid(self, form):
return super().form_invalid(form)
def get_success_url(self):
user_id = self.kwargs["user_id"]
summative_id = self.kwargs["summative_id"]
return reverse(
"myapp:summative_detail",
kwargs={
"user_id": user_id,
"summative_id": summative_id,
},
)
As you can see in the template - I render the SubdomainProficiencyLevel objects with the template variable {{ choice }}
I have tried doing {{ choice.description }} or {{ choice.name }} <span class="bold">{{ choice.description }}</span> but then nothing displays.
I have also tried adjusting the __str__ method on the model - which changes there work, but do not render as HTML (just as a string as expected).
What is the best way to customize that in the HTML?
I ended up creating a custom radio button class (similar to the documentation)
class CustomRadioSelect(forms.RadioSelect):
def create_option(
self, name, value, label, selected, index, subindex=None, attrs=None
):
option = super().create_option(
name, value, label, selected, index, subindex, attrs
)
if value:
option["attrs"]["description"] = value.instance.description
return option
Using that in the form:
subdomain_proficiency_level = forms.ModelChoiceField(
empty_label="Undecided",
queryset=SubdomainProficiencyLevel.objects.none(),
widget=CustomRadioSelect(),
required=False,
)
Then I could access it like this in the template:
{{ choice.data.attrs.description }}

Django - Custom ModelMultipleChoiceField can't categorize choices based on their parent model

EDITS AVAILABLE BELOW!
My goal:
Category1
----Option1
----Option2
--Option3
Category2
----Option1
----Option2
etc.
I have a parent model (Venue) and a child model (Amenity). A venue can have many amenities.
while configuring my initial data and presenting it with {{form.as_p}} everything works as expected.
But when I try to render my own custom form, so that I can apply a loop, It doesn't pre-populate them.
Here is my template:
<form method="POST" class="ui form">
{% csrf_token %}
{% for category in categories %}
<h4 class="ui horizontal divider header">
<i class="list icon"></i>
{{category.category}}
</h4>
<p class="ui center aligned text"><u>{{category.description}}</u></p>
{% for amenity in category.amenity_set.all %}
<div class="inline field">
<label for="choices_{{amenity.id}}"></label>
<div class="ui checkbox">
<input id="choices_{{amenity.id}}" type="checkbox" value="{{amenity.id}}" name="choices">
<label><span data-tooltip="{{amenity.description}}" data-position="top left">{{amenity}}</span></label>
</div>
</div>
{% endfor %}
{% endfor %}
<button type="submit" name="submit" class="ui button primary">Next</button>
</form>
my ModelForm:
class AmenitiesForm(ModelForm):
class Meta:
model = Venue
fields = ('choices',)
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=forms.CheckboxSelectMultiple,)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs.get('instance'):
initial = kwargs.setdefault('initial', {})
initial['choices'] = [c.pk for c in kwargs['instance'].amenity_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self)
instance.amenity_set.clear()
instance.amenity_set.add(*self.cleaned_data['choices'])
return instance
and my views.py:
class AddAmenitiesView(LoginRequiredMixin, CreateView):
"""
AddAmenitiesView is the view that prompts the user to select the amenities of their venue.
"""
model = Venue
form_class = AmenitiesForm
template_name = 'venues/add_amenities.html'
def parent_venue(self):
"""
returns the parent_venue based on the kwargs
:return:
"""
parent_venue = Venue.objects.get(id=self.kwargs["venue_id"])
return parent_venue
def get_initial(self):
initial = super().get_initial()
initial['choices'] = self.parent_venue().amenity_set.all()
return initial
def form_valid(self, form):
venue = Venue.objects.get(id=self.kwargs['venue_id'])
form.instance = venue
# form.instance.owner = self.request.user
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["parent_venue"] = self.parent_venue()
context["categories"] = AmenitiesCategory.objects.all()
return context
def get_success_url(self):
return reverse('add-amenities', kwargs={'venue_id': self.object.id,})
I suppose it has to do with my template since rendering the form normally, it does prepopulate the model.
Thank you for taking the time!
EDIT:
With Raydel Miranda's answer below I managed to edit the templates for how the form gets presented:
forms.py:
class CustomAmenitiesSelectMultiple(CheckboxSelectMultiple):
"""
CheckboxSelectMultiple Parent: https://docs.djangoproject.com/en/2.1/_modules/django/forms/widgets/#CheckboxSelectMultiple
checkbox_select.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/checkbox_select.html
multiple_input.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/multiple_input.html
checkbox_option.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/checkbox_option.html
input_option.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/input_option.html
"""
template_name = "forms/widgets/custom_checkbox_select.html"
option_template_name = 'forms/widgets/custom_checkbox_option.html'
class AmenitiesForm(ModelForm):
class Meta:
model = Venue
fields = ('choices',)
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=CustomAmenitiesSelectMultiple,)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs.get('instance'):
initial = kwargs.setdefault('initial', {})
initial['choices'] = [c.pk for c in kwargs['instance'].amenity_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self)
instance.amenity_set.clear()
instance.amenity_set.add(*self.cleaned_data['choices'])
return instance
custom_checkbox_select.html:
{% with id=widget.attrs.id %}
<div class="inline field">
<div {% if id %} id="{{ id }}" {% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}" {% endif %}>
{% for group, options, index in widget.optgroups %}{% if group %}
<div>
{{ group }}
<div>
{% if id %} id="{{ id }}_{{ index }}" {% endif %}>{% endif %}{% for option in options %}
<div class="checkbox">{% include option.template_name with widget=option %}</div>
{% endfor %}{% if group %}
</div>
</div>
{% endif %}{% endfor %}
</div>
</div>
{% endwith %}
custom_checkbox_option.html :
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>
As requested, also my models.py:
class TimeStampedModel(models.Model):
"""
An abstract base class model that provides self-updating
"created" and "modified" fields.
"""
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class VenueType(TimeStampedModel):
type = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.type
class Venue(TimeStampedModel):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=250)
type = models.ForeignKey(VenueType, on_delete=models.CASCADE)
total_capacity = models.PositiveIntegerField(default=0)
description = models.TextField(blank=False)
contact_number = PhoneNumberField(blank=True)
contact_email = models.EmailField(blank=True)
published = models.BooleanField(default=False)
def __str__(self):
return self.name
class AmenitiesCategory(TimeStampedModel):
category = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.category
class Amenity(TimeStampedModel):
category = models.ForeignKey(AmenitiesCategory, on_delete=models.CASCADE)
venues = models.ManyToManyField(Venue, blank=True)
space = models.ManyToManyField(Space, blank=True)
name = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.name
class Meta:
ordering = ['category']
You said while configuring my initial data and presenting it with {{form.as_p}} everything works as expected, if so, use {{ form.choices }} in order to render that field.
<form method="POST" class="ui form">
{% csrf_token %}
{{form.choices}}
<button type="submit" name="submit" class="ui button primary">Next</button>
</form>
Then, what you need is have a custom CheckboxSelectMultiple with its own template (in case you want a custom presentation to the user), and use it in your form:
Custom CheckboxSelectMultiple could be:
class MyCustomCheckboxSelectMultiple(CheckboxSelectMultiple):
template_name = "project/template/custom/my_checkbox_select_multiple.html"
And in the form:
class AmenitiesForm(ModelForm):
# ...
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=forms.MyCustomCheckboxSelectMultiple)
# ...
How to implement the template my_checkbox_select_multiple.html, is up to you.
If you're using some Django prior to 1.11, visit this link to learn about a others things you've to do in order to customize a widget template.
Django widget override template
Hope this help!

Display foriegnkey fields in Django template for a CreateView

I am trying to display a checklist in the CreateView using the values in the ForeignKey fields for descriptions.
models.py
class Structure(models.Model):
name = models.CharField(max_length = 30)
description =models.CharField(max_length = 300, null=True, blank=True)
def __str__(self):
return self.name
class SelectedFramework(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
structure = models.ForegignKey(Structure)
selected = models.BooleanField(default = False)
views.py
class FrameworkCreateView(generic.CreateView):
model = SelectedFramework
fields =['structure', 'selected']
template_name = 'catalogue/structure.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super(FrameworkCreateView, self).form_valid(form)
structure.html
{% extends 'catalogue\base.html' %}
{% block container %}
<h2>{% block title %}Structures{% endblock title %}</h2>
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
<div class="col-sm-10">{{form.structure}} {{form.selected}}</div><br>
{% endfor %}
</div>
</form>
{% endblock %}
The code above works but will display the ForeignKey 'structure' as a dropdown list with the values of __str__. Is there a way to display string for structure.name and structure.description with the checkbox from selected in the CreateView?
In your template use:
{{ form.structure.name }}
{{ form.structure.description}}
You can write custom form, override the save method and create Structure object manually there:
class FrameworkForm(forms.ModelForm):
structure_name = forms.CharField(required=True)
structure_description = forms.CharField(required=False)
class Meta:
model = SelectedFramework
fields = [
'structure_name', 'structure_description', 'selected'
]
def save(self, commit=False):
instance = super(FrameworkForm, self).save(commit=False)
structure = Structure(
name=self.cleaned_data.get('structure_name'),
description=self.cleaned_data.get('structure_description')
)
structure.save()
instance.structure = structure
instance.save()
return instance
Also add form_class = FrameworkForm to your view instead of fields = ['structure', 'selected']
EDIT:
Perhaps you want something like this:
<ul>
{% for structure in form.fields.structure.choices.queryset %}
<li>{{ structure.name }} - {{ structure.description }}</li>
{% endfor %}
</ul>
If you want to get fields by iterating in the template. You have to use-
{% for field in form %}
{{ field }}
{% endfor %}
don't have to use any dot notation to get the field. If you want to get the label of the field you can use {{ field.label}} usually before {{field}}

Django: ModelFormSet saving first entry only

Update:
The issue seemed to be in the coding for Django-formset. I was processing it as an inline formset and not a model formset. The answer below was also correct. Thanks!
I am working with a model formset for an intermediate model. I am using django-formset js to add additional formset fields on the template. Most everything works OK except that when I go to save the formset only the first entry is being saved to the DB. The first entry is saved and assigned correctly but any after than just disappear. It is not throwing any errors so I am not sure what is going wrong. Thanks!
The Model
class StaffAssignment(models.Model):
study = models.ForeignKey(Study, related_name='study_set', null=True, on_delete=models.CASCADE)
staff = models.ForeignKey('account.UserProfile', related_name='assigned_to_set', null=True, on_delete=models.CASCADE)
role = models.CharField(max_length=100, null=True)
assigned_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-role',)
def __str__(self):
return '{} is assigned to {}'.format(self.staff, self.study)
The Form:
class AddStaff(forms.ModelForm):
model = StaffAssignment
fields = ('staff',)
def __init__(self, *args, **kwargs):
super(AddStaff, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({'class': 'form-control'})
The View:
def add_staff(request, study_slug):
study = get_object_or_404(Study, slug=study_slug)
staff_formset = modelformset_factory(StaffAssignment, form=AddStaff, fields=('staff',), can_delete=True)
if request.method == 'POST':
staffList = staff_formset(request.POST, request.FILES)
if staffList.is_valid():
for assignment in staffList:
assigned = assignment.save(commit=False)
assigned.study = study
assigned.role = assigned.staff.job_title
assigned.save()
return HttpResponseRedirect(reverse('studies:studydashboard'))
else:
HttpResponse('Something is messed up')
else:
staffList = staff_formset(queryset=StaffAssignment.objects.none())
return render(request, 'studies/addstaff.html', {'staffList': staffList, 'study': study})
The Template:
<form action="{% url 'studies:addstaff' study.slug %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="box-body">
{% for list in staffList %}
<div class="form-group" id="formset">
{% if list.instance.pk %}{{ list.DELETE }}{% endif %}
{{ list.staff }}
{% if list.staff.errors %}
{% for error in list.staff.errors %}
{{ error|escape }}
{% endfor %}
{% endif %}
</div>
{% endfor %}
{{ staffList.management_form }}
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
You are not including the primary key field in the template, as required by the docs. Add
{% for list in staffList %}
{{ list.pk }}
...
{% endfor %}

Group django formset into groups based on foreign key

I have a simple setup of InventoryItems and Categories. I have a formset of InventoryItems but want to split up the items based on the FK Category, I don't need or want an inline form set.
Simplified version of what I have
class Category(models.Model):
name = models.CharField(max_length=255)
inventory = models.BooleanField(default=False)
class Inventory(models.Model):
name = models.CharField(max_length=255)
quantity = models.IntegerField()
category = models.ForeignKey(Category)
def viewOfForm(request):
categories = Category.objects.filter(inventory=True)
InventoryFormset = modelformset_factory(Inventory, can_delete=True, extra=1)
formset = InventoryFormset(request.POST or None, queryset=Inventory.objects.filter(category__inventory=True))
return render_to_response('template.html', locals())
What I would like to do in the template
{% for category in categories %}
<fieldset class="collapse">
<h2>{{ category.name }}</h2>
{% for form in formset %}
{% if form.category == category %}
{{ form }}
{% endif %}
{% endfor %}
</fieldset>
{% endfor %}
You only need a small change to get this working; use form.instance.category in your if template tag:
{% if form.instance.category == category %}