I'm using Django 1.4 with Python 2.7 on Ubuntu 12.04.
I'm going to continue to update this question as I make progress.
UPDATE 2:
I'm trying to generate 2 choice fields in a form using information from an existing model.
class AssignProject(forms.Form):
def __init__(self, devs, *args, **kwargs):
"""
.. method:: __init__()
Class constructor
:param devs: Tuple with which developer
"""
super(AssignProject, self).__init__(*args, **kwargs)
self.dev = forms.ChoiceField(widget = forms.Select(), choices = devs, required = True)
self.designer = forms.ChoiceField(widget = forms.Select(), choices = devs, required = True)
At this point I can't seem to access dev and designer ChoiceField in my template yet.
Here is the view:
#login_required
def view_all_projects(request):
"""
.. function:: view_projects()
Show the projects
:param request: Django Request object
"""
data = { 'user' : request.user }
if (request.user.is_authenticated() and request.user.is_superuser):
all_projects = Projects.objects.filter(active = True)
dev_info = User.objects.filter(is_staff = True, is_superuser = False)
dev_dict = {}
for dev in dev_info:
dev_dict[dev.id] = '{0} {1}'.format(dev.first_name, dev.last_name)
devs = tuple(dev_dict.items())
form = AssignProject(devs)
data.update({ 'form' : form })
data.update({ 'projects' : all_projects })
data.update(csrf(request))
return render_to_response("view_all_projects.html", data)
return render_to_response("index.html", data)
I have verified that the developers/designers are properly getting set in the devs tuple.
...and the template from view_all_projects.html:
<form action="/assignProject/" method="post">{% csrf_token %}
<table>
<td>
<input type="hidden" name="project_id" value={{ project.id }}>
<tr>
<td align="right"><label class="formlabel">Assign Developer:<br /></label></td><td>{{ form.dev }}</td>
</tr>
<tr>
<td align="right"><label class="formlabel">Assign Designer:<br /></label></td><td>{{ form.designer }}</td>
</tr>
<tr>
<td align="right"><label class="formlabel"> </label></td><td><input type="submit" value="Submit ►"></td>
</tr>
</td>
</table>
</form>
I don't see any errors, but I do see a strange object reference in place of the ChoiceField in the template.
<django.forms.fields.ChoiceField object at 0x7ffdbc054190>
<django.forms.fields.ChoiceField object at 0x7ffdbc0542d0>
I see these instead. I know I'm close...just can't quite get what I'm going wrong.
Thoughts?
The issue was entirely in the form __init__.
I should have assigned the fields like follows:
class AssignProject(forms.Form):
def __init__(self, devs, *args, **kwargs):
"""
.. method:: __init__()
Class constructor
:param devs: Tuple with developers
"""
super(AssignProject, self).__init__(*args, **kwargs)
self.fields['dev'] = forms.ChoiceField(widget = forms.Select(), choices = devs, required = True)
self.fields['designer'] = forms.ChoiceField(widget = forms.Select(), choices = devs, required = True)
Note the self.fields['dev'] not self.dev. Fixed everything.
Related
Due to my lack of skills I think I overcomplicate and would like to get input for a simple solution. I'm creating kind of a web shop template. I have a service work order that I would like to add parts to. I have a decent looking template where I can list all parts that fits a machine model and then filter through them.
One Workorder many Parts (So I have one separate class for all parts objects for workorder). SO to avoid to have to update part number by part number, using create view. I have tried to use a listview where I try to select all parts at the same time.
In my CBV I define the filtering and get a context "Partslist" with all parts to show in table. In the template I have created an input fields qty. I.e. all part in the "Partslist" gets a "qty" inputfields.
In my post or save definition can I simply catch all input fields data with a self.request.GET statement somehow, or could I use the context "Partslist" to iterate through the fields?
models.py
class WorkOrderParts(models.Model):
wo_num = models.CharField(max_length=30, verbose_name='Order Number')
wo_pa_number = models.CharField(max_length=30, verbose_name='Part Number')
wo_pa_qty_def = models.DecimalField(max_digits=3,decimal_places=0,verbose_name='Qty Planned for')
class Parts(models.Model):
pa_number = models.CharField(max_length=30,verbose_name='Part Number')
pa_group1 = models.CharField(max_length=30,verbose_name='Group of Parts')
pa_group2 = models.CharField(max_length=30,verbose_name='2:nd Group of Parts')
pa_fits = ArrayField(models.CharField(max_length=20,verbose_name='models that part fit to (BRAND-MODEL)'),default=list, null=True, blank=True)
views.py
class WO_PartCreate(LoginRequiredMixin,ListView):
login_url = '/login'
template_name = 'AddPart.html'
model = WorkOrderParts
def get_context_data(self, **kwargs):
model = 'my machine model'
qs=Parts.objects.filter(pa_fits__icontains=model)
context = super().get_context_data(**kwargs)
context['Partslist'] = qs
return context
def post(self):
??? This is where I want to take all parts with input qty>0 and update new objects into WorkOrderParts.
AddPart.html
<table>
<thead>
<tr>
<th class="text-left">Part Number</th>
<th class="text-left">Quantity</th>
</tr>
</thead>
{% for item in Partslist %}
<tbody>
<tr>
<td class="text-left">
{% autoescape off %}{{item.pa_number}}{% endautoescape %}<br>
</td>
<td class="text-left">
<input type="number" method="GET" name="{{ item.pa_number }}-Qty"/>
</td>
</tr>
</tbody>
I hope I have been able to explain enough for someone to understand me.The reason why I use a table is simply that I have much more data in there that I want to display.
OK I know there is most probably better ways to solve this. But since I at least was able to find out something that worked, I thought I would share it with others.
It at least solved my immediate need to grab input fields from a dynamic list and do something with it.
views.py
class AddPartTest(LoginRequiredMixin,ListView):
login_url = '/login'
template_name = 'AddPart.html'
model = Parts
form_class = Add_Parts2 ##including selected fields
def get_context_data(self, **kwargs):
order = self.kwargs.get('str')
wo = WorkOrder_main.objects.get(order=order)
model = wo.wo_machine_model
qs=Parts.objects.filter(pa_fits__icontains=model)
first_contains_query = self.request.GET.get('pa_group1')
if is_valid_queryparam(first_contains_query):
qs=qs.filter(pa_group1=first_contains_query)
second_contains_query = self.request.GET.get('pa_group2')
if is_valid_queryparam(second_contains_query):
qs=qs.filter(pa_group2= second_contains_query)
context = super().get_context_data(**kwargs)
context['Partslist'] = qs
return context
def post (self, request, *args, **kwargs):
order = self.kwargs.get('str')
context = self.get_context_data()
Partslist_x = context['Partslist']
Parts_num_list = Partslist_x.values_list('pa_number', flat=True)
for part in Parts_num_list:
qty = part + 'Qty'
Qty = self.request.POST.get(qty)
if int(Qty)==0:
pass
else:
WorkOrderParts.objects.create(
wo_num=order,
wo_pa_number=part,
wo_pa_qty_def=Qty,
)
path='where I want to end up'
return redirect (path)
This is my model:
class dateEvent(models.Model):
venue = models.ForeignKey(Venue, on_delete=models.CASCADE)
event = models.ForeignKey('Event', on_delete=models.CASCADE)
start_date_time = models.DateTimeField(auto_now=False, auto_now_add=False)
My views.py:
def event_edit_view(request, id):
event = get_object_or_404(Event, id=id)
#MyOtherForm instantiated by event
DateEventFormSet = inlineformset_factory(Event, dateEvent, extra=5, can_delete=True, fields=('event', 'start_date_time', 'venue', 'link', 'link_description'),
widgets={
'venue': s2forms.Select2Widget(),
'start_date_time': CalendarWidget(),
form_date_event = DateEventFormSet(request.POST or None, instance=Event.objects.get(id=id), prefix="dateEvent", queryset=dateEvent.objects.filter(event__id=id))
if request.method == "POST":
if MyOtherForm.is_valid() and form_date_event.is_valid():
MyOtherForm.save()
form_date_event.save()
return redirect('my-events')
else:
raise forms.ValidationError(form_date_event.errors) #?
context = {
[other forms...]
'form_date_event': form_date_event,
}
return render(request, "events/template.html", context)
And my template.html:
<table id="dateEvent">
<thead>
<th>Venue</th>
<th>Date and time</th>
<th>Link</th>
<th>Link description</th>
</thead>
<tbody id='date_body'>
{{ form_date_event.management_form }}
{% for formDate in form_date_event.forms %}
<tr class="form-row-dateEvent" style='display:table-row;'>
<td>{{formDate.venue}}<br>
Add a new venue</td>
<td>{{ formDate.start_date_time}}</td>
<td>{{formDate.link}} {{formDate.this_composition}}</td>
<td>{{formDate.id}}{{formDate.link_description}}</td>
</tr>
{% endfor %}
</tbody>
</table>
Now, how can I enforce the user to fill in both venue and the date? As it is now if a user fills in the venue field and leaves the corresponding date field empty, Django redirects the user to an ugly yellow page
ValidationError at /private/event-edit/1150/
['This field is required.']
Request Method: POST
Request URL: .../event-edit/1150/
Django Version: 3.1
Exception Type: ValidationError
Exception Value:
['This field is required.']
Exception Location: /home/.../.../.../views.py, line 682, in event_edit_view
if DEBUG=TRUE is set, otherwise an even uglier Error 500. Is there a way that the user would get a nice 'please fill in this field' message in the same row next to the populated venue field, when they hit the 'submit' button?
PS
Do I need to declare a custom clear() method, according to which if a record has a venue needs also to have a date populated?
I think an issue is inside your event_edit_view function inside the views.py file, you should pass the formset in the context inside the POST method condition
do this in your else part instead of raising the validation error. It should work now.
def event_edit_view(request, id):
.....
if request.method == "POST":
.....
if MyOtherForm.is_valid() and form_date_event.is_valid():
MyOtherForm.save()
form_date_event.save()
return redirect('my-events')
else:
context = {
[other forms...]
'form_date_event': form_date_event,
}
return render(request, "events/template.html", context)
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 ]
Django: 1.4.1
Model:
class Hoja(models.Model):
nombre = models.CharField(max_length=200) # requerido
class Linea(models.Model):
hoja = models.ForeignKey(Hoja) # requerido
nombre = models.CharField(max_length=200) # requerido
padre = models.ForeignKey('self', null=True, blank=True, related_name='hijo')
View:
lineas = Linea.objects.filter(hoja=alt).order_by('id')
LineaHojaSet = modelformset_factory(Linea, can_delete=True, extra=1 if request.POST.has_key('siguiente') else 0)
formset = LineaHojaSet(request.POST or None, queryset=lineas)
if request.method=='POST':
# process formset
return render_to_response('template.html', {'formset':formset}, context_instance=RequestContext(request))
Template:
<table>
<thead>
<tr><th>Nombre</th><th>Borrar</th></tr>
</thead>
<tbody>
{% for fs in formset %}
<tr>
<td>{{ fs.nombre }}</td>
<td>{{ fs.id }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<input type="submit" name="siguiente" value="Añadir siguiente" />
When I submit the "siguiente" button, I can see than the formset is getting the correct extra field of 1, but in the webpage, the only rows showing are the database ones. It's this a bug, or I'm doing something wrong?
Formset factory finds number of forms either by max_num, extra parameters or form-TOTAL_FORMS parameter in request.POST (or data) from management form.
In your case, request.POST['form-TOTAL_FORMS'] has number which does not include extra form . So it does not add extra form when you create formset.
One solution would be to increment this number by one when your condition is met. e.g.
data = None
if request.POST:
data = request.POST.copy() #required as request.POST is immutable
if request.POST.has_key('siguiente'):
data['form-TOTAL_FORMS'] = int(data['form-TOTAL_FORMS']) + 1
#now use data instead of request.POST
formset = LineaHojaSet(data, queryset=lineas)
....
However, there are some drawbacks of manipulating formset this way. When you validate formset, the extra form will show errors if there are any required fields.
Better solution would be to create formset again before passing it template with one extra form and queryset. Most likely, when formset is valid, you would save any new objects, those will get added by queryset. So your page will show newly added objects and one extra form.
lineas = Linea.objects.filter(hoja=alt).order_by('id')
LineaHojaSet = modelformset_factory(Linea, can_delete=True,)
formset = LineaHojaSet(request.POST or None, queryset=lineas)
if request.method=='POST':
# process formset
if formset.is_valid:
#saved and done with formset.
if request.POST.has_key('siguiente'):
LineaHojaSet = modelformset_factory(Linea, can_delete=True, extra=1)
formset = LineaHojaSet(queryset=lineas)
...
return render_to_response('template.html', {'formset':formset}, context_instance=RequestContext(request))
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?