Cannot pass initial data to a formset - django

In my Django app i have the following:
form:
class GameForm(forms.ModelForm):
class Meta:
model = Game
def __init__(self, *args, **kwargs):
super(GameForm, self).__init__(*args, **kwargs)
curr_tournament = Tournament.objects.get(id=self.instance.tournament.id)
self.queryset = Game.objects.filter(tournament=curr_tournament)
self.fields['opponent_black'].queryset = curr_tournament.participants
self.fields['opponent_white'].queryset = curr_tournament.participants
in views.py:
GameFormSet = formset_factory(GameForm)
later then:
games = Game.objects.filter(tournament=tournament_id).order_by('number_of_game_in_tour', 'number_of_tour')
gameformset = GameFormSet(initial=list(games.values()))
Finally i'm passing gameformset into template and rendering it like that:
<form>
{{ gameformset.management_form }}
{% for gameform in gameformset %}
{{ gameform }}
{% endfor %}
</form>
This all leads to an error:
'auto_id' is an invalid keyword argument for this function
I cannot see the reason why this can happen? Maybe you do?

Related

DRY approaches for displaying fields in a ListView with custom [exclude]

I am writing a generic template that I can use across all my models that require a ListView.
To do this, I know I can simply create a generic table in my template with a for loop over the object_list, but as each model is different I can't capture all the fields this way.
Instead I have created a (abstract) method that each model inherits, which produces a list of fields, names and values:
class MyModel(models.Model):
def get_display_fields(self, exclude_fields=[], adminonly_fields=[]):
"""Returns a list of all field names on the instance."""
fields = []
for f in self._meta.fields:
fname = f.name
# resolve picklists/choices, with get_xyz_display() function
get_choice = 'get_' + fname + '_display'
if hasattr(self, get_choice):
value = getattr(self, get_choice)()
else:
try:
value = getattr(self, fname)
except AttributeError:
value = None
if f.editable and f.name not in (exclude_fields or adminonly_fields):
fields.append(
{
'label': f.verbose_name,
'name': f.name,
'help_text': f.help_text,
'value': value,
}
)
return fields
I can then use this in my template which works universally across any model:
{% for obj in object_list %}
{% for f in obj.get_display_fields %}
<p>{{f.label}}</p>
<p>{{f.name}}</p>
<p>{{f.value}}</p>
{% endfor %}
{% endfor %}
Where I am stuck, is I want to allow some customisation of the exclude_fields and adminonly_fields in the view (which is on the model method). For example:
class MyGenericView(ListView):
exclude_fields = ['field1', 'field2']
adminonly_fields = ['field3',]
How can I pass these lists to get_display_fields?. I know I can just write them into the model method, but that defeats the point of this DRY approach. Can I append it to/modify the queryset somehow?
I don't want to use editable=False as I want to allow each view that subclasses MyGenericView to provide excluded_fields as an option.
Create a custom template tag that takes an argument. You will need to use the {% load %} tag to make it available.
It's important that you use a simple tag so that you can pass multiple arguments from your view.
from django import template
register = template.Library()
#register.simple_tag
def get_display_fields(obj, adminonly_fields=[], excluded_fields=[]):
if hasattr(obj, 'get_display_fields')
return obj.get_display_fields(adminonly_fields, excluded_fields)
return []
Pass adminonly_fields and excluded_fields as extra context data in your view so it can be used with your template tag.
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['adminonly_fields'] = self.adminonly_fields
context['excluded_fields'] = self.excluded_fields
return context
Then in your template.
{% for obj in object_list %}
{% get_display_fields obj adminonly_fields excluded_fields as display_fields %}
{% for f in display_fields %}
<p>{{f.label}}</p>
<p>{{f.name}}</p>
<p>{{f.value}}</p>
{% endfor %}
{% endfor %}

Using "in" operator in Django template not working as expected

Could really use help figuring out what I'm misunderstanding about the following: I'm using the built-in "in" operator to check if a user (standard user model) is in a queryset of Board members.
This is the Board member model:
class BoardMembers(models.Model):
board = models.ForeignKey(Board, related_name="memberships", blank=True)
user = models.ForeignKey(User,related_name='user_boards', blank=True)
member_role = models.CharField(choices=MEMBER_ROLES, max_length=100, blank=True)
def __str__(self):
return self.user.username
Here is the view I'm using:
class ViewBoard(SelectRelatedMixin, generic.DetailView):
model = models.Board
select_related = ("user",)
template_name = 'board/view_board.html'
def get_context_data(self, **kwargs):
context = super(ViewBoard, self).get_context_data(**kwargs)
context['boardmembers_list'] = BoardMembers.objects.filter(board__slug=self.kwargs['slug'])
return context
And this is the html part I'm struggling with:
{% if user in boardmembers_list %}
<h1>HEY, {{ user.username }}, YOU'RE ALREADY A MEMBER!</h1>
{% else %}
<a class="btn btn-primary" href="{% url 'board:join_board' slug=board.slug pk=board.pk %}">Join this Board</a>
{% endif %}
I tested to see if individually the objects could be retrieved in the template and both the user and the boardmembers_list show up correctly.
From the docs it looks like this should be a relatively straightforward thing to do. Can anyone tell me where I'm going wrong or what I'm misunderstanding about how these operators work?
I think its better to decide if the user is in board or not in the View, instead of template.
class ViewBoard(SelectRelatedMixin, generic.DetailView):
...
def get_context_data(self, **kwargs):
board_mem_list = BoardMembers.objects.filter(board__slug=self.kwargs['slug'])
context = super(ViewBoard, self).get_context_data(**kwargs)
context['boardmembers_list'] = board_mem_list
context['user_in_board'] = board_mem_list.filter(user=self.request.user).exists() # this will check if user is already in boardmemberlist
return context
In template:
{% if user_in_board %}
<h1>HEY, {{ user.username }}, YOU'RE ALREADY A MEMBER!</h1>
{% else %}
<a class="btn btn-primary" href="{% url 'board:join_board' slug=board.slug pk=board.pk %}">Join this Board</a>
{% endif %}
And you are trying to search userin boardmemebers_list, and it won't work because user is a User model instance and boardmemebers_list is a queryset of BoardMember model where user is just a field of that model.

django-selectable populate AutocompleteSelectField on loading form

I succesfully implemented the django-selectable AutoCompleteSelectField in a simple form that lets you enter an note description and a corresponding domain category ( domain and foreign key picked from other Many-to-One relationship
See: most relevant code:
# MODEL
class Note(models.Model):
notetext = models.TextField(default='nota')
domain = models.ForeignKey(Domain)
def __str__(self):
return self.notetext
def get_absolute_url(self):
return reverse('note:note_detail', args= [self.id])
# FORM
class NoteForm(forms.ModelForm):
domainselect = AutoCompleteSelectField(lookup_class= DomainLookup, label='Pick a domain category', required=True,)
def __init__(self, *args, **kwargs):
super(NoteForm, self).__init__(*args, **kwargs)
domaintext = self.instance.domain.title
self.fields['domainselect'].widget = AutoCompleteSelectWidget(DomainLookup , { 'value': self.instance.domain.title } )
def save(self, commit=True):
self.instance.domain = self.cleaned_data['domainselect']
return super(NoteForm, self).save(commit=commit)
class Meta:
model = Note
fields = ('notetext',)
widgets = {
'domain' : AutoCompleteSelectWidget(DomainLookup), }
# VIEW
class EditNoteView(generic.edit.UpdateView):
model = Note
form_class = NoteForm
success_url = "/note/"
def get_queryset(self):
base_qs = super(EditNoteView, self).get_queryset()
return base_qs.filter()
def get_object(self):
object = get_object_or_404(Note,id=self.kwargs['id'])
return object
# TEMPLATE
{% extends "base_sidebar.html" %}
{%block content%}
<form action="" method="post">
{{form.as_p}}
<button type="submit">Save</button>
{% csrf_token %}
{% load selectable_tags %}
{{ form.media.css }}
{{ form.media.js }}
</form>
{%endblock%}
Now, when an existing record is selected for editing via generic.edit.UpdateView in a Modelform, I want to populate the AutocompleteSelectField with the corresponding values ( domain description and id ) formerly saved into the database upon loading the form.
By overwriting the init(self, *args, **kwargs) method of the NoteForm, I was able to get almost this far in the sense that the first HTML input field gets populated.
However, the hidden input value gets set to the same value and pushing the save button results in posting a non valid form as if no domain category was selected.
Here's the page source that is sent back to the Browser:
<p><label for="id_domainselect_0">Pick a domain:</label>
<input data-selectable-allow-new="false" data-selectable-type="text" data-selectable-url="/selectable/domain-domainlookup/" id="id_domainselect_0" name="domainselect_0" type="text" value="politics" />
<input data-selectable-type="hidden" id="id_domainselect_1" name="domainselect_1" type="hidden" value="politics" /></p>
I don't know how to change the context (by setting self.fields['domainselect'].widget) in order to get the title into the domainselect_0 input value and the corresponding pk into the hidden domainselect_1 input value. ?
Thanks for helping me out.
After digging down into the django-selectable and Django code it appears the AutocompleteSelectWidget is based on the Django forms.MultiWidget class.
The Django MultiWidget accepts 1 single value (list) that is decomposed into the values corresponding to the respective 'subWidgets' through a mechanism implemented in a decompress method. ( see https://github.com/mlavin/django-selectable/blob/master/selectable/forms/widgets.py class SelectableMultiWidget )
So, all you have to do is assign a list containing title and id to the widget:
def __init__(self, *args, **kwargs):
super(NoteForm, self).__init__(*args, **kwargs)
self.initial['domainselect'] = [self.instance.domain.title , self.instance.domain.id ]

Referencing a dynamic number of fields in a template in django

It is all very simple. I have this form:
class add_basketForm(forms.Form):
def __init__(self, selected_subunits, *args, **kwargs):
self.selected_subunits = selected_subunits
super(add_basketForm, self).__init__(*args, **kwargs)
for subunit in self.selected_subunits:
self.fields['su%d' % (subunit['unit__id'])] = forms.IntegerField()
The number of subunits are unknown. I would like to use something like this (you get the idea):
{% for unit in selected_subunits %}
{{ form.su%s }} % (unit.unit__id)
{% endfor %}
But of course that doesn't work. My question is how do I reference those formfields in Django template language?
In order to access the BoundField instances for your dynamic field instances, which is what gives you access to all of the attributes and methods necessary to render the field, you need to access the field objects using the form of form.fieldname rather than form.fields[fieldname]
Here's a potential refactoring of your form class:
class add_basketForm(forms.Form):
def __init__(self, selected_subunits, *args, **kwargs):
super(add_basketForm, self).__init__(*args, **kwargs)
for subunit in self.selected_subunits:
self.fields['su%d' % (subunit['unit__id'])] = forms.IntegerField()
def su_fields(self):
for name in self.fields:
if name.startswith('su'):
yield(self[name])
Then in your template, you should be able to iterate over the fields as you would normally expect by accessing form.su_fields:
{% for su_field in form.su_fields %}
....
{% endfor %}
(I had been struggling with this same problem for several hours. Thanks to this answer from Carl Meyer and this article on dynamic form generation from Jacob Kaplan-Moss for pointing me in the right directions.)
Group those fields in an additional list and then simply iterate over this list.
In __init__:
self.subunit_list = []
for subunit in self.selected_subunits:
field = forms.IntegerField()
self.fields['su%d' % (subunit['unit__id'])] = field
self.subunit_list.append(field)
In template:
{% for field in form.subunit_list %}
...
{% endfor %}
To correct gruszczy's answer, this code worked for me:
In __init__ of your form:
self.subunit_list = []
for subunit in self.selected_subunits:
field = forms.IntegerField()
self.fields['su%d' % (subunit['unit__id'])] = field
self.subunit_list.append(self['su%d' % (subunit['unit__id'])])
In your template:
{% for field in form.subunit_list %}
<!-- show form field (inputbox) -->
{{ field }}
{% endfor %}

CharField values disappearing after save (readonly field)

I'm implementing simple "grade book" application where the teacher would be able to update the grades w/o being allowed to change the students' names (at least not on the update grade page). To do this I'm using one of the read-only tricks, the simplest one. The problem is that after the SUBMIT the view is re-displayed with 'blank' values for the students. I'd like the students' names to re-appear.
Below is the simplest example that exhibits this problem. (This is poor DB design, I know, I've extracted just the relevant parts of the code to showcase the problem. In the real example, student is in its own table but the problem still exists there.)
models.py
class Grade1(models.Model):
student = models.CharField(max_length=50, unique=True)
finalGrade = models.CharField(max_length=3)
class Grade1OForm(ModelForm):
student = forms.CharField(max_length=50, required=False)
def __init__(self, *args, **kwargs):
super(Grade1OForm,self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['student'].widget.attrs['readonly'] = True
self.fields['student'].widget.attrs['disabled'] = 'disabled'
def clean_student(self):
instance = getattr(self,'instance',None)
if instance:
return instance.student
else:
return self.cleaned_data.get('student',None)
class Meta:
model=Grade1
views.py
from django.forms.models import modelformset_factory
def modifyAllGrades1(request):
gradeFormSetFactory = modelformset_factory(Grade1, form=Grade1OForm, extra=0)
studentQueryset = Grade1.objects.all()
if request.method=='POST':
myGradeFormSet = gradeFormSetFactory(request.POST, queryset=studentQueryset)
if myGradeFormSet.is_valid():
myGradeFormSet.save()
info = "successfully modified"
else:
myGradeFormSet = gradeFormSetFactory(queryset=studentQueryset)
return render_to_response('grades/modifyAllGrades.html',locals())
template
<p>{{ info }}</p>
<form method="POST" action="">
<table>
{{ myGradeFormSet.management_form }}
{% for myform in myGradeFormSet.forms %}
{# myform.as_table #}
<tr>
{% for field in myform %}
<td> {{ field }} {{ field.errors }} </td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
Your way of displaying the readonly field is the problem.
Since the student field is disabled, the form submit will not have that as the input, so the error form that is displayed with validation error messages don't get the initial value.
That is why ReadOnly Widget has to be more complex than just being a html disabled field.
Try using a real ReadOnlyWidget, one that overrides _has_changed.
Following is what I use. For instantiation, it takes the original_value and optionally display_value, if it is different.
class ReadOnlyWidget(forms.Widget):
def __init__(self, original_value, display_value=None):
self.original_value = original_value
if display_value:
self.display_value = display_value
super(ReadOnlyWidget, self).__init__()
def _has_changed(self, initial, data):
return False
def render(self, name, value, attrs=None):
if self.display_value is not None:
return unicode(self.display_value)
return unicode(self.original_value)
def value_from_datadict(self, data, files, name):
return self.original_value
I'm stretching myself a little here, so some thoughts:
% Have you sniffed the traffic to see exactly what's being sent between browser and server?
% Do you need to send the student name as a hidden field (your db update thing may assume you want student blank if you don't)?
% Have you looked at the source of your HTML after Python parses it?