Formsets. Initial data cannot be displayed in ModelChoiceField - html

In my application, the user can create and edit reports. When rendering the report edit page, the fields are filled with initial data.
I ran into the following problem - one of the fields (operator) does not display the value that I pass to it in the view through the initial argument. Instead, the field either remains empty or takes on a default value, depending on the settings. All other fields are filled with initial data without errors.
I'm using Formsets, the problem field is ModelChoiceField. The initial data is taken from the json array.
As far as I could understand the HTML code - in the problem field "operator", in the "option" tag (it seems to be generated by the Select widget) the "selected" attribute is missing. But how to put this attribute there is not clear. Adding to the confusion is that the "eq_type" field, which is also a select field, is displayed without errors. "selected" appears next to the desired option.
Maybe someone came across a similar one? Please, tell me what could be the problem.
fragment of forms.py:
class AMSEquipmentForm(forms.Form):
EQ_TYPES = (
('panel_antenna', 'панельная антенна'),
('RRL_antenna', 'РРЛ антенна'),
('radio_module', 'радиомодуль')
)
eq_type = forms.ChoiceField(choices=EQ_TYPES, label='Тип')
height = forms.IntegerField(label='Высота')
proportions = forms.IntegerField(label='Размеры')
amount = forms.IntegerField(label='Количество')
manufacturer = forms.CharField(max_length=50, label='Производитель')
model = forms.CharField(max_length=50, label='Модель')
operator = forms.ModelChoiceField(queryset=Operator.objects.all(), label='Оператор') # Проблемное поле
note = forms.CharField(max_length=100, label='Примечание')
eq_type.widget.attrs.update({'class': 'formset-field'})
height.widget.attrs.update({'class': 'formset-field'})
proportions.widget.attrs.update({'class': 'formset-field'})
amount.widget.attrs.update({'class': 'formset-field'})
manufacturer.widget.attrs.update({'class': 'formset-field'})
model.widget.attrs.update({'class': 'formset-field'})
operator.widget.attrs.update({'class': 'formset-field'}) # Проблемное поле
note.widget.attrs.update({'class': 'formset-field'})
fragment of views.py:
def report_update(request, pk):
EquipmentFormset = formset_factory(AMSEquipmentForm, max_num=1)
report = Report.objects.get(idReport=pk)
form = ReportModelForm(instance=report)
formset = EquipmentFormset(initial=json.loads(report.reportEquipAms), prefix='reports_report') # Здесь передаю начальные данные
if request.method == 'POST':
form = ReportModelForm(request.POST or None)
formset = EquipmentFormset(request.POST or None, prefix='reports_report')
if form.is_valid() and formset.is_valid():
report.reportYear = form.cleaned_data['reportYear']
report.reportObject_id = form.cleaned_data['reportObject']
report.reportTemplate_id = form.cleaned_data['reportTemplate']
report.reportTeam_id = form.cleaned_data['reportTeam']
report.reportEquipment_id = form.cleaned_data['reportEquipment']
report.reportWind = form.cleaned_data['reportWind']
report.reportWeather = form.cleaned_data['reportWeather']
report.reportSoil = form.cleaned_data['reportSoil']
report.reportTemp = form.cleaned_data['reportTemp']
report.reportWeather3 = form.cleaned_data['reportWeather3']
report.reportElVoltage = form.cleaned_data['reportElVoltage']
report.reportElCableL = form.cleaned_data['reportElCableL']
report.reportElCableR = form.cleaned_data['reportElCableR']
report.reportElRope = form.cleaned_data['reportElRope']
report.reportElBus = form.cleaned_data['reportElBus']
report.reportMeasuresDate = form.cleaned_data['reportMeasuresDate']
report.reportData = form.cleaned_data['reportData']
report.reportEquipAms = json.dumps(formset.cleaned_data, cls=OperatorEncoder, ensure_ascii=False)
report.save()
return redirect('reports:report-list')
context = {
'report': report,
'form': form,
'formset': formset
}
return render(request, "reports/report_update.html", context)
fragment of models.py:
class Operator(models.Model):
idOperator = models.AutoField(primary_key=True)
operatorName = models.CharField(max_length=45)
operatorReport = models.ForeignKey("Report", null=True, on_delete=models.SET_NULL)
operatorContract = models.ForeignKey("Contract",null=True, on_delete=models.SET_NULL)
def __str__(self):
return self.operatorName
class Meta:
verbose_name = "Оператор"
verbose_name_plural = "Операторы"
Displaying fields in the browser as viewed through the code inspector:
field eq_type:
<select name="reports_report-1-eq_type" class="formset-field" id="id_reports_report-1-eq_type">
<option value="panel_antenna">панельная антенна</option>
<option value="RRL_antenna" selected>РРЛ антенна</option>
<option value="radio_module">радиомодуль</option>
</select>
field operator:
<select name="reports_report-1-operator" class="formset-field" id="id_reports_report-1-operator">
<option value="8">Оператор3</option>
<option value="9">Оператор2</option>
<option value="10">Оператор1</option>
</select>
A fragment of the html template that is responsible for the form:
{% for equipment in formset %}
<tr class="item">
<td>
{{ equipment.eq_type }}
</td>
<td>
{{ equipment.height }}
</td>
<td>
{{ equipment.proportions }}
</td>
<td>
{{ equipment.amount }}
</td>
<td>
{{ equipment.manufacturer }}
</td>
<td>
{{ equipment.model }}
</td>
<td>
{{ equipment.operator }}
</td>
<td>
{{ equipment.note }}
</td>
<td>
<button type="button" class="btn btn-danger btn-sm remove-form-row" id="{{ formset.prefix }}">Delete</button>
</td>
</tr>
{% endfor %}
update
The initial data that I transfer in reportEquipAms looks like this:
[
{
'eq_type': 'panel_antenna',
'height': 18,
'proportions': 10,
'amount': 1,
'manufacturer': 'manufacturer_name',
'model': 'some model',
'operator': 'Оператор2', # Operator class object
'note': 'some note'
},
]
The list can contain one or more dictionaries. Based on the number of fields in the formset.

#NKSM told me in comment that I should pass the primary key of the Operator object in the "operator" field. I passed the name of the object there.
I rewrote OperatorEncoder:
class OperatorEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Operator):
return obj.idOperator
return json.JSONEncoder.default(self, obj)
Now everything works as it should.

Related

I can't save data of <select> in html

I have django application with form.
in model
LIVING_COUNTRIES = [
('AFGANISTAN', 'Afganistan'),
('ALBANIA', 'Albania'),
('ALGERIA', 'Algeria'),
('ANGORRA', 'Andorra')]
class Employee(models.Model):
country_living = models.CharField(max_length=50, choices=LIVING_COUNTRIES, default=FRESHMAN, blank=True, null=True)
in html
<select name="country" id="id_category" data="{{ data.country }}">
{% for each in living_countries_list %}
<option value="{{ each.0 }}" class="living_countries">{{ each.1 }}</option>
{% endfor %}
</select>
in view
context['has_error'] = True
if context['has_error']:
employees = models.Employee.objects.all()
living_countries_list = LIVING_COUNTRIES
return render(request, 'authentication/employee_register.html', context)
So I have form rendered in get() method of class-based view. When I click submit post method checks for errors. If there are any context['has_error'] is set to true. For simplycity I've written it down anyway. And if it is true we render form again. I want to have previous choice of user still displayed in html. I've done it on other inputs by `data="{{ data.field }}". How would I do that here?

How can I edit manytomanyfield data

I am trying to edit manytomanyfield data. Here is my model
class Permission(models.Model):
shop = models.ForeignKey(Shop, on_delete=models.SET_NULL, null=True)
permission_title = models.CharField(max_length=255)
class Meta:
ordering = ["-id"]
def __str__(self):
return self.permission_title
class Roles(models.Model):
shop = models.ForeignKey(Shop, on_delete=models.SET_NULL, null=True)
role_title = models.CharField(max_length=255)
permissions = models.ManyToManyField(Permission)
class Meta:
ordering = ["-id"]
def __str__(self):
return self.role_title
Here in these models i have a model called permission. this model is in a manytomanyfield relation with Roles model. I want to edit this manytomanyfield. I have already did the creation part. But now i want to edit the data and I want to show the data in the templates, I chose while creating a role. I want to show it an input type checkox
Here is my views:-
def createRolesView(request, shop_id):
shopId = get_object_or_404(Shop, pk=shop_id)
permissions = Permission.objects.filter(
shop=shopId.id
)
if shopId.user == request.user:
if request.method == "POST":
role_title = request.POST.get("role_title")
shop = Shop.objects.get(id=shopId.id)
permissions = request.POST.getlist("permissions")
rl = Roles(
role_title = role_title,
shop = shop,
)
rl.save()
for p in permissions:
rl.permissions.add(p)
rl.save()
return redirect(f"/adminpanel/roles/{shopId.id}/")
args = {
"shopId": shopId,
"permissions": permissions,
}
return render(request, "roles/create-role.html", args)
else:
return redirect("warning")
def editRolesView(request, role_id, shop_id):
shopId = get_object_or_404(Shop, pk=shop_id)
roleId = get_object_or_404(Roles, pk=role_id)
if shopId.user == request.user:
if request.method == "POST":
roleId.role_title = request.POST.get("role_title")
shop = Shop.objects.get(id=shopId.id)
# roleId.permissions_set.all()
args = {
"shopId": shopId,
"roleId": roleId,
}
return render(request, "roles/edit-role.html", args)
else:
return redirect("warning")
Suggestions only, I haven't coded this myself.
[Edit. This question caused an "itch", so I coded it up to conform I was right. Skip ahead for a pretty generic edit M2M view ]
Two simple tables
One showing the current permissions assigned to the role, which has the queryset roles.permissions.all(), along with a "remove" checkbox for each.
The other showing the available permissions with an "add" checkbox for each. The queryset might be permissions.exclude( roles__pk=this_role.pk) (to exclude those already added, although it's harmless to add something already added). You should also exclude any permissions which this roles instance is not allowed to acquire.
You might use Django Formsets, but it's also rather easy to process raw data from request.POST in this case. Your template would basically be a form containing instances of
<input type="checkbox" name="add" value="{{permission.pk}}" >
and
<input type="checkbox" name="remove" value="{{permission.pk}}" >
with appropriate descriptive text gathered from the permission instance.
If it were me I'd display them as tables left and right, but as long as they are inside a <form> with Submit (and Done?) buttons, it's not relevant.
In your POST handling, you'd get the checked pk values with request.POST.getlist('add') and request.POST.getlist('remove'). For each one, revalidate that it was in the original queryset that was rendered into a form, and if the pk values are acceptable
role.permissions.add( permission) # or .remove( permission)
(where permission is the Permissions instance that's been revalidated)
After Submit of the above you'd probably redirect back to the same again so that the user can see that the requested additions and removals have happened. The "Done" button will redirect elsewhere( if "Done" in request.POST ... )
[Edit] The result of causing me an itch I had to scratch ....
class GenericEditM2MView( DetailView):
#model = Model # required as per DetailView
# template_name = # as per DetailView
#m2m_fieldname = None # no longer required if unique: the name of the model's m2m field to operate on
remove_qs = None # defaults to .all() of the m2m field
success_url = '.' # defaults to displaying the modified m2m relationship unless done
done_url = None # where to go if submit="Done", defaults to success_url
"""
template_name must define a form full of checkboxes, obtained from
{% for p in add_qs %}
<input type="checkbox" name="add" value="{{p.pk}}" > {% endfor}
{% for p in remove_qs %}
<input type="checkbox" name="remove " value="{{p.pk}}" > {% endfor}
default is to return to this same view after submit, to show that the changes
have been made. You can supply <input type="submit" name="submit" value="done" />
which will go to done_url instead of success_url
example use:
class PenStockM2MView( GenericEditM2MView):
template_name = 'playpen/edit_m2m.html'
model = PenStock
# m2m_fieldname = 'name' # works it out if ony one M2M field on the model
done_url = '/playpen/OK'
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# everything works without this __init__ provided self.m2m_fieldname is present and correct.
# if model has only one m2m field, locate it via _meta as default.
# Also check m2m_fieldname is m2m because very confusing errors later if it's not!
f = getattr(self, 'm2m_fieldname', None)
m2m_fieldnames = [ field.name for field in self.model._meta.get_fields() if field.many_to_many ]
model_name = self.model.__name__
if f and not f in m2m_fieldnames:
raise AttributeError( f'field "{f}" is not a many-to-many field in {model_name}')
if not f:
if len( m2m_fieldnames ) == 1:
self.m2m_fieldname = m2m_fieldnames[0]
else:
raise AttributeError( f'Cannot identify a unique many-to-many field in {model_name}' )
def get_add_queryset(self):
field = getattr( self.object, self.m2m_fieldname)
remove_qs = self.get_remove_queryset()
already_there = remove_qs.values_list('pk', flat=True)
return remove_qs.model.objects.exclude( pk__in = already_there) # is qs.model documented?
def get_remove_queryset(self):
if hasattr( self, 'remove_queryset'):
return self.remove_qs
remove_qs = getattr( self.object, self.m2m_fieldname)
return remove_qs.all()
def get_context_data( self, **kwargs):
context = super().get_context_data( **kwargs)
context['add_qs'] = self.get_add_queryset()
context['remove_qs'] = self.get_remove_queryset()
return context
def post( self, request, *args, **kwargs):
self.object = self.get_object()
add = request.POST.getlist('add')
remove = request.POST.getlist('remove')
add_objs = list( self.get_add_queryset().filter(pk__in=add) )
remove_objs = list( self.get_remove_queryset().filter(pk__in=remove) )
field = getattr( self.object, self.m2m_fieldname )
field.add( *add_objs)
field.remove( *remove_objs)
return HttpResponseRedirect( self.get_done_url() or self.get_success_url() )
def get_success_url(self):
return self.success_url
def get_done_url( self):
done = self.request.POST.get("submit", None)
if done == "done" and hasattr(self, 'done_url'):
return self.done_url
return None
And here's a template (not completely generic. Using Bootstrap in base.html.)
<div class="row">
<div class="col-md-3 col-sm-4 col-xs-6">
<h1> Remove </h1>
<table class="table tbl">
{% for p in remove_qs %}
<tr><td>{{p.name }}</td><td><input type="checkbox" name="remove" value="{{p.pk}}" form="the-form" class="BigCheckbox"></td></tr>
{%endfor %}
</table>
</div>
<div class="col-md-3 col-sm-4 col-xs-6">
<h1> Add </h1>
<table class="table tbl">
{% for p in add_qs %}
<tr><td>{{p.name }}</td><td><input type="checkbox" name="add" value="{{p.pk}}" form="the-form" class="BigCheckbox"></td></tr>
{%endfor %}
</table>
</div>
</div>
<div class="row">
<form method="post" id="the-form"> {% csrf_token %}
<input type="submit" name="submit" value="submit" />
 
<input type="submit" name="submit" value="done" />
</form>
</div>

How do I use a for loop to reuse a django form in a template

After struggling with this issue for a while, I am hoping someone here can point me in a more productive direction.
I am trying to take an indeterminate number of variables in a database (obtained through a different template) and render them on a webpage, each variable with a simple data entry form, to be saved back to the database. Basically, it's a tracker for analysis. Say I want to track my daily sleep, running time, and calorie intake (the variables). I have those saved in a database as variables and want to call upon those variables and show them on a webpage with a daily entry form. I am using a "for" loop right now and it renders the way I want it to, with the variable name and the form, but it is only saving the last item in the variable list. How do I amend the code below such that when I hit the save button for each form rendeded, it saves the information for that variable (not just the last one rendered).
Below is the code. Any and all help would be infinitely appreciated.
Models...
class Variable(models.Model):
date_added = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(get_user_model(), default='', on_delete=models.CASCADE) # id the active user
ENTRY_TYPE_CHOICES = [
('numeric', 'enter a daily number'),
('scale', 'rate daily on a scale of 1-10'),
('binary', "enter daily, 'yes' or 'no' "),
]
variable = models.CharField(max_length=50, default='')
entry_type = models.CharField(max_length=50, choices=ENTRY_TYPE_CHOICES, default="numeric")
def __str__(self):
return self.variable
class DailyEntry(models.Model):
date = models.DateField(default=datetime.date.today)
# date_added = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(get_user_model(), default='', on_delete=models.CASCADE) # id the active user
variable_name = models.CharField(max_length=50, default='')
variable_id = models.SmallIntegerField(default=0000)
entry = models.FloatField(default=9999)
class Meta:
verbose_name_plural = 'Daily Entries'
def __str__(self):
return self.variable
Form...
class VariablesForm(forms.ModelForm):
class Meta:
model = Variable
fields = ['variable', 'entry_type' ]
labels = {'variable':'Dependent variable to track', 'entry_type': 'Type of measure'}
class DailyEntryForm(forms.ModelForm):
class Meta:
model = DailyEntry
fields = ['variable_name', 'variable_id', 'entry', 'date']
labels = {'entry': 'Daily entry', 'date': 'Date'}
widgets = {'variable_name': forms.HiddenInput(), 'variable_id': forms.HiddenInput()}
Views...
def daily_entry(request):
''' page to make daily entries '''
vars = Variable.objects.filter(id__gt = 0 )
if request.method != 'POST':
# No data submitted. GET submitted. Create a blank form
form = DailyEntryForm()
else:
#POST data submitted. Process data
form = DailyEntryForm(data=request.POST)
if form.is_valid():
data = form.save(commit=False)
data.created_by = request.user
data.save()
return HttpResponseRedirect(reverse('entry_new'))
context = {'form': form, 'vars': vars}
return render(request, 'entry_new.html', context)
and HTML...
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
{% for var in vars %}
<div>
<ul>
<h3>{{ var.variable }}</h3>
<form class="" action="" method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="hidden" name="variable_id" value="{{ var.id }}" >
<input type="hidden" name="variable_name" value="{{ var.variable }}">
<input type="submit" name="" value="Save" />
</ul>
</div>
{% endfor %}
{% endblock content %}
Any help, well, helps...
Thanks!

Is it possible to loop a custom model form in django?

Is it possible to create multiple objects for a model in django by looping the same form in a for loop. I am using a custom model form.
My template is:
{% for query in queryset %}
<form method="POST" action="{% url 'academics' %}" style=" padding: 5%">
{% csrf_token %}
<input type="text" name="Student" class="form-control" id="id_Student"
value="{{query}}">
<input type="text" name="Subject" class="form-control" required id="id_Subject">
<input type="checkbox" name="Presence" id="id_Presence">
<button type="Submit" id="submit">Submit</button>
{% endfor %}
<button type="Submit" id="submit">Submit</button>
</form>
My models.py is:
class Attendance(models.Model):
Student = models.CharField(max_length=100, blank=False)
Hour = models.CharField(max_length=1, blank=False)
Subject = models.CharField(max_length=8, blank=False)
Date = models.DateTimeField(default=timezone.now)
Presence = models.BooleanField(default=False, blank=False)
def __str__(self):
return f'{self.Student}'
My views.py is:
def academics(request):
if request.user.is_staff:
form = forms.AttendanceForm()
context = {
'form': form,
'queryset': User.objects.filter(profile__Year='SY',profile__Department='CSE')
}
if request.method == "POST" :
form = forms.AttendanceForm(request.POST)
if form.is_valid():
student = request.POST.get('Student')
hour = request.POST.get('Hour')
subject = request.POST.get('Subject')
boolean = request.POST.get('Presence')
def bool(boolean):
if boolean == 'on':
return 'True'
else:
return 'False'
form = Attendance(Student=student,Hour=hour,Subject=subject,Presence=bool(boolean))
form.save()
return render(request, 'console/academics.html',context)
Currently i can create multiple objects, but with the same values of the last form. ie, the object is created with the values of last form. Here i have looped the form so that n number of forms will be generated for n queries with the name filled automatically in the first field. I know explaining this is little complex. Anyone can help?
I'm not entirely clear what you mean by "looping a form", but if you want the user to be able to enter a list of arbitrary length of similar sets of data, then what you want is a Formset or a ModelFormset. When it comes back, you validate all the data that he has submitted, and if it's all good then you iterate through it, usually creating or modifying multiple objects.
Due to reputation I'm unable to comment but I believe this is how you achieve your desired result. by using WHILE LOOP.
I myself have not much knowledge of python & Django but I guess this is the logic. Please correct me if I am wrong instead of down voting.
var = 0
n = 5
if request.method == "POST":
form = forms.AttendanceForm(request.POST)
if form.is_valid():
while var < n:
student = request.POST.get('Student')
hour = request.POST.get('Hour')
subject = request.POST.get('Subject')
boolean = request.POST.get('Presence')
def bool(boolean):
if boolean == 'on':
return 'True'
else:
return 'False'
form = Attendance(Student=student, Hour=hour,Subject=subject,Presence=bool(boolean))
form.save()
var += 1
return render(request, 'console/academics.html', context)

Django Model Boolean Field needs to be initially unchecked. initial=False required=False is not working

Forms.py
class CheckPostedForm(forms.ModelForm):
posted = forms.BooleanField(required=False, initial=False)
Views.py
postform = CheckPostedForm(request.POST, instance=author)
if postform.is_valid():
postform.save()
else:
print 'Cannot save post form.'
Models.py
posted = models.CharField(max_length=2, blank=True, null=True)
class Meta:
model = TmpPlInvoice
exclude = ['net_amt', 'post_date', 'address', 'particulars', 'pos_code', 'acct', 'cust', 'voucher_id', 'voucher_date', 'post_date']
labels = {
'posted': 'Posted',
}
I want a checkbox which is initially unchecked and based on th value from DB i check/uncheck it. No matter what i do the form always comes out as checked="checked".
print postform gives this,
<tr><th><label for="id_posted">Posted:</label></th><td><input checked="checked" id="id_posted" name="posted" type="checkbox" value="n" required /></td></tr>
I have searched alot every where, the docs say that it should be initially false required=False if we want to apply conditions.
Detail
Views.py
def master_detail_posted(request):
if request.method == 'GET':
author = TmpPlInvoice.objects.get(id=1)
postform = CheckPostedForm(instance=author)
return render(request,'main.html' ,{'postform': postform})
if request.method == 'POST':
author = TmpPlInvoice.objects.get(id=1)
postform = CheckPostedForm(request.POST, instance=author)
print postform
if postform.is_valid():
logger.info('saving post form %s', postform.cleaned_data)
postform.save()
else:
logger.info('post form is not valid %s %s', postform.errors, request.POST)
return render(request,'main.html' ,{'postform': postform})
Template
<div> {{ postform }} </div>
You've hard coded the field in the template. Clearly, Django can't change HTML tags that you've written explicitly.
You need to use Django tags in your template:
<td>{{ form.posted }}</td>