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))
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)
Here's my domain model:
class Seat(models.Model):
name = models.CharField(max_length=8)
class Event(models.Model):
...
class Occupation(models.Model):
event = models.ForeignKey(Event, related_name='occupations')
seat = models.ForeignKey(Seat)
number = models.CharField(max_length=20)
(Seat is a small table, like 5-10 records.)
We have an UI requirements for a event edit page to look like this:
[1-01] [enter number]
[1-02] [enter number]
[1-03] [enter number]
[2-01] [enter number]
[2-02] [enter number]
[2-03] [enter number]
User navigates to event's occupation page where they see a list of all seats from system and prompted to fill numbers from the external source into the system.
Since the seats table is pretty small and to prevent errors like choosing same seat twice, we're required to display all seats pre-fill into the form and locked, so the user can't change seat selection and only limited to enter corresponding numbers.
Also, seats can be added or removed, so we can't make a "static" form with 6 predefined rows.
I suppose it should be a Django's inline model formset with a form like
class OccupationForm(forms.ModelForm):
class Meta:
model = Occupation
fields = ('seat', 'number')
...
But I'm not sure how should I display a prefilled form, prevent an user from changing seats (and not just a client-side locking via disabled or javascript)
First set seat widget to HiddenInput and add the seat_name property to the form. This property will be used later in the HTML template:
class OccupationForm(forms.ModelForm):
class Meta:
model = Occupation
fields = ('seat', 'number')
widgets = {'seat': forms.HiddenInput}
def __init__(self, *args, **kwargs):
super(OccupationForm, self).__init__(*args, **kwargs)
self.fields['number'].required = False
self.seat_name = self.initial['seat'].name
Then populate initial data with the seats and numbers for the event. Pass this initial to the formset. Validate and save formset as usual:
from django.forms.formsets import formset_factory
def update_seats(request, event_id):
event = Event.objects.get(pk=event_id)
numbers = dict((o.seat, o.number) for o in event.occupations.all())
initial = [{'seat': seat, 'number': numbers.get(seat, '')}
for seat in Seat.objects.all()]
OccupationFormSet = formset_factory(OccupationForm,
min_num=len(initial), validate_min=True,
max_num=len(initial), validate_max=True,
extra=0)
if request.method == 'POST':
formset = OccupationFormSet(request.POST, initial=initial)
if formset.is_valid():
for form in formset:
seat = form.initial['seat']
number = form.cleaned_data.get('number', '').strip()
if number:
Occupation.objects.update_or_create(
event=event, seat=seat,
defaults={'number': number})
else:
Occupation.objects.filter(event=event, seat=seat).delete()
return redirect('.')
else:
formset = OccupationFormSet(initial=initial)
return render(request, 'update_seats.html', {'formset': formset})
And update_seats.html template in which we show seat name as {{ form.seat_name }}:
<form action="" method="post">
{% csrf_token %}
<table>
{{ formset.management_form }}
<tr>
<th>Seat</th>
<th>Number</th>
</tr>
{% for form in formset %}
<tr>
<td>[{{ form.seat_name }}]{{ form.seat }}</td>
<td>{{ form.number }}</td>
</tr>
{% endfor %}
</table>
<button>Update</button>
</form>
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.
I'm trying to build a page for an inventory system that will allow a user to update a quantity of items received.
I want to show a table of all products and let the user enter the quantity received, which I'll post and iterate over to update the database.
Here is my view:
def new_shipment(request):
list_of_active_products = Product.objects.filter(status=1)
ShipmentFormSet = formset_factory(ShipmentForm, extra=0)
formset = ShipmentFormSet(initial=list_of_active_products)
return render_to_response('inventory/new_shipment.html', {'formset': formset})
Here's my model for the form:
class ShipmentForm(forms.Form):
sku = forms.IntegerField()
product_name = forms.CharField(max_length=100)
quantity = forms.IntegerField()
And here is the form template:
<form method="post" action="">
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
<input type="submit" />
</form>
And here is the error I'm getting:
Caught AttributeError while rendering: 'Product' object has no attribute 'get'
Can anyone help me out with this?
From the docs it looks like you have to pass in a list of dictionaries as the initial data, rather than a QuerySet:
Also note that we are passing in a list of dictionaries as the initial data.
You may want to change your initial query to:
list_of_active_products = Product.objects.filter(status=1).values()
which will return a list of dictionaries rather than model-instance objects.
Using initial data with a formset:
https://docs.djangoproject.com/en/dev/topics/forms/formsets/#using-initial-data-with-a-formset
ValuesQuerySet:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.values
You can also use the queryset argument. This should work:
formset = ShipmentFormSet(queryset=list_of_active_products)
cf. https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#changing-the-queryset