Django 1.11 Form to create multiple instances of model - django

I am using Django 1.11 for my first Django/Python project and have been stuck for a few days trying to understand Formsets in order to create a form that will allow the creation of multiple model instances through one form. Any assistance that could be provided to help me understand and pass this issue will be greatly appreciated!
The way I have been approaching it is as follows:
I have the following model:
class Task(models.Model):
client_name = models.ForeignKey(Client,null=True,blank=True,on_delete=models.DO_NOTHING)
description = models.CharField(max_length=255)
due_date = models.DateField(null=True,blank=True,default=datetime.now)
assigned = models.ForeignKey(User,on_delete=models.DO_NOTHING)
I have the following form based on the model:
class FNTaskForm(ModelForm):
class Meta:
model = Task
exclude = ()
I have created the folllowing FormSet based on the above model & form:
TaskFormSet = modelformset_factory(Task, form = FNTaskForm, exclude=(),extra=2)
In views.py I have:
class FNTaskCreate(CreateView):
model = Task
form_class = FNTaskForm
template_name = 'fntasks.html'
def get_context_data(self, **kwargs):
context = super(FNTaskCreate, self).get_context_data(**kwargs)
context['formset'] = TaskFormSet(queryset=Task.objects.none()) # providing none
return context
And finally I render it in my html template as:
<form method="post">
{% csrf_token %}
<table class="table link-formset">
{% for form in formset %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle row1 row2 %} formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
<a class="btn btn-default" href="{% url 'clients' %}">Cancel</a>
<input type="submit" value="Next" class="btn btn-primary" />
</form>
The form appears as follows on the page:
The issue I am having and that which I am gratefully seeking on help on resolving has 2 parts:
Part 1. I cannot successfully save data into the model using this form. I am not getting any errors however I believe that the form has 3 instances, not just the 2 instances displayed and it is a third (not visible instance) that is blank and hence not allowing the form to be valid. The reason I believe this is related to issue 2 (below). When I insert print my kwargs.pop variable to test it I get 3 outputs of 'None'. So I think in my ignorance and inexperience with Django I have not used the model formset factory correctly.
Part 2. I would also like to use kwargs to set the initial value of the 'Client Name' field using the kwarg 'clientlist' passed in the URL. I have used the following to confirm the kwarg but the result is (None None None):
def __init__(self, *args, **kwargs):
clientlist = kwargs.pop('clientlist', None)
super().__init__(*args, **kwargs)
print(clientlist)
The URL is as follows for passing the kwarg:
url(r'^tasks/create/(?P<clientlist>\d+)/$',views.FNTaskCreate.as_view(),name='fntask_create'),
Is anyone able to help me pass these issues to use modelformset_factory (or any other method that is better recommended) to successfuly save multiple model instances with one form with kwargs passed into the initial fields on the form? Thanking you for your time!

Related

How to reassign a model to a different app for display only purposes in Django Admin

I know I can change the display title for a model in Django Admin using
class Meta:
verbose_name='Custom Model Name Here'
However, is there a way to display which app heading a model is displayed under?
For example, if I create a custom user model Users in a new app also called users then the default user model goes from Authentication and Authorization > Users to Users > Users.
I would like to retain it under the original heading Authentication and Authorization > Users.
I have read this answer which suggests changes the app verbose_name, however it only changes the verbose name of the app associated with the model. I want to show the model in a different group on the admin panel. You can see the issue that approach takes here:
You could set up a proxy model
In app_1.models.py
class MyModel(models.Model):
...
In app_2.models.py
from app_1.models import MyModel
class MyModelProxy(MyModel):
class Meta:
proxy = True
Then register the MyModelProxy in admin as normal
There is no simple way of doing what you're intending, check out the Django code/template that generates the view:
#never_cache
def index(self, request, extra_context=None):
"""
Display the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""
app_list = self.get_app_list(request)
context = {
**self.each_context(request),
'title': self.index_title,
'app_list': app_list,
**(extra_context or {}),
}
request.current_app = self.name
return TemplateResponse(request, self.index_template or 'admin/index.html', context)
templates/admin/index.html:
{% for app in app_list %}
<div class="app-{{ app.app_label }} module">
<table>
<caption>
{{ app.name }}
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.admin_url %}
{% if model.view_only %}
<td>{% trans 'View' %}</td>
{% else %}
<td>{% trans 'Change' %}</td>
{% endif %}
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
As you can see, Django admin template is looping your app_list directy, so the only way of doing it would be to override the admin/index.html template to place your models in your desired order.
From Django docs:
For those templates that cannot be overridden in this way, you may still override them for your entire project. Just place the new version in your templates/admin directory. This is particularly useful to create custom 404 and 500 pages.
https://docs.djangoproject.com/en/2.2/ref/contrib/admin/#overriding-vs-replacing-an-admin-template
In firstapp.models.py
class ActualModel(models.Model):
class DisplayModel(ActualModel):
class Meta:
proxy = True
in secondapp.admin.py
from firstapp.models import DisplayModel
admin.site.register(DisplayModel)
Easier way to do that:
In model class Meta add
app_label = 'app2'
db_table = 'app1_modelname'
so you assigning model to another app with app_label and assigning db_table with old (current) table name to prevent making migrations and table renami

Displaying an Image in ModelForm

I have the following model:
class Portfolio(models.Model):
id = models.AutoField(primary_key=True)
member = models.ForeignKey(Member, on_delete=models.CASCADE)
image = models.ImageField(upload_to='portraits/', default='/images/some_image.png')
For that I made the ModelForm:
class PortfolioForm(forms.ModelForm):
class Meta:
model = Portfolio
exclude = ['id']
I need many of those in one template so I am creating them in the Following way in my view
def portfolio_form(request, pk):
...
formset = PortfolioFormSet(queryset=Portfolio.objects.filter(pk__in=list_of_ids))
for x in p]
finally in the html I have this:
<form enctype="multipart/form-data" action="{% url 'elec:portfolio_form' pk=id %}" method="POST">{% csrf_token %}
...
{{ formset.management_form }}
{% for form in formset.forms %}
<tr>{% for field in form %}
{% if field.name == 'image' %}
<td><img src="{{field.image.url}}"></td>
{% else %}
<td align="center" valign="middle">{{field}}</td>
{% endif %}
{% endfor %}</tr>
{% endfor %}
<input type="submit" class="btn btn-default" value="Save and Refresh" />
</form>
Now what I expect to see is the image that I am saving into that Model. The if statement is coming back as false but why?. What am I doing wrong? Everywhere I looked it should work like this.
Also I have added the request.FILES in PortfolioForm with that the instance is not called how can I fix that?
OK I found the solution in Stackoverflow since I used some other words to find a solution.
what you need to do in the template instead of :
{<td><img src="{{field.image.url}}"></td>}
is this :
{<td><img src="{{form.instance.image.url}}"></td>}

Why is not there a reusable template for Django's DetailView?

Displaying forms in a template is rather easy in Django:
<form action="" method="post">{% csrf_token %}
{{ form }}
<input type="submit" value="Update" />
</form>
It is basically just one word - display the {{ form }}. It is so simple that you can use the same template for different forms.
You can limit the fields to be shown on the form using the fields = [] list if you are using CBV's such as CreateView or UpdateView.
Drawing parallel to this, one expects to have a similar workflow for showing the models as well (as opposed to editing) such as in DetailView. But, there is no such thing.. You have to write a custom template for every DetailView that you use. Such as:
<h3>User: {{ user }}</h3>
<label>First Name</label>: {{ user.first_name }} <br />
<label>Last Name</label>: {{ user.last_name }} <br />
<label>Username</label>: {{ user.username }} <br />
<label>School</label>: {{ user.person.school.name }} <br />
This is very similar to what the {{ form }} would generate, except for the field values printed here, as opposed toinputs being printed there.
So, I wonder, why isn't there a reusable generic template for DetailView's? Is there a technical limitation for this, or is it just not as reusable as I imagine?
I have created and have been using gladly for about a year now my own generic templates. So, I wanted to share, here it is:
Creating a view is as simple as this:
class PersonDetail(DetailViewParent):
model=Person
DetailViewParent used above (override fields and exclude as needed; default is to include all):
class DetailViewParent(DetailView):
fields=[]
exclude=[]
template_name='common/modal_detail.html'
def get_context_data(self, **kwargs):
context=super(DetailViewParent, self).get_context_data(**kwargs)
context['exclude']=self.exclude
context['fields']=self.fields
return context
Relevant part of the template:
{% fields %}
{% for name, label, value, is_link in fields %}
<tr>
<td><strong>{{ label|capfirst }}</strong></td>
<td>
{% if value.get_absolute_url and request.is_ajax %}
<a class="modal-loader" href="{{ value.get_absolute_url }}">{{ value }}</a>
{% elif value.get_absolute_url %}
{{ value }}
{% else %}
{% if is_link and request.is_ajax %}
<a class="modal-loader" href="{{ value }}">{{ value }}</a>
{% elif is_link %}
{{ value }}
{% else %}
{{ value }}
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
And the template tags:
#register.tag(name="fields")
def generate_fields(parser, token):
"""
{% fields %} - loads field name, label, value, is_link to the context
"""
args=token.contents.split()
object_name='object'
if len(args) == 2:
object_name=args[1]
return FieldsNode(object_name)
class FieldsNode(template.Node):
"""
called by generate_fields above
"""
def __init__(self, object_name):
self.object_name=object_name
def render(self, context):
# Get the data necessary for rendering the thing, and add it to the context.
try:
obj=template.Variable(self.object_name).resolve(context)
except template.VariableDoesNotExist:
return ''
include_fields=context.get("fields", None)
exclude_fields=context.get("exclude", None)
fields=[]
for field in obj._meta.fields:
name=field.name
if exclude_fields and name in exclude_fields:
continue
if include_fields and name not in include_fields:
continue
label=field.verbose_name
value=getattr(obj, field.name)
is_link=(type(field).__name__ in ('URLField',))
if isinstance(value, bool):
value=get_bool_check_mark(value)
elif value is None:
value=''
fields.append((
name, label, value, is_link,
))
# If include_fields was defined, then sort by the order.
if include_fields:
fields=sorted(fields, key=lambda field_: include_fields.index(field_[0]))
context['fields']=fields
return ''
The template might be customized to your needs and liking. But I would like to note two things:
1) get_absolute_url: if this (standard django) model method is defined, the field value is shown as url.
2) modal-loader class: this triggers js on the client side to show the detail view in a bootstrap 3 modal. Furthermore, if clicked on a link as mentioned in 1) that is loaded onto the same modal, thus making it easier to browse detail views. It has also a "back" button to go back to the previous model's view. I am not including that here because it is a lot of code, and beyond the scope of this question.
I think it is not as reusable as you imagine.
It might conceivably be possible to define "standard" ways to render simple model properties like CharField - this quickly becomes impossible when you get into more complex relational fields like ManyToManyField, ForeignKey, OneToOneField. You would end up overriding any default representation very quickly for anything but the simplest of models.
Secondly Django is not - and should not be - opinionated about what your models are for, and therefore it makes sense that it doesn't try to assume how you want to render them.
This is different from forms where the structure of individual form fields is defined in Django and in HTML, and there is a strong correlation between the two.

Model Formset - By Default model formset is rendering one extra field (2 fields in total)

My model formset without even defining "extra" parameters in modelformset_factory is rendering one extra field in the template. I have tried many variations but it didn't work. If I print the form (the model form) on the command line it just prints a single form field as required but on model formset it prints 2 by default.
Here is my code.
models.py
class Direction(models.Model):
text = models.TextField(blank=True, verbose_name='Direction|text')
forms.py
class DirectionForm(forms.ModelForm):
class Meta:
model = Direction
fields = ['text',]
views.py
def myview(request):
Dirset = modelformset_factory(Direction, form=DirectionForm)
if request.method == "POST":
dir_formset = Dirset(request.POST or None)
if dir_formset.is_valid():
for direction in dir_formset:
text = direction.cleaned_data.get('text')
Direction.objects.create(text=text)
return render(request, "test/test.html", {'DirFormSet':Dirset})
template
{% block content %}
<form method="POST">{% csrf_token %}
<div id="forms">
{{DirFormSet.management_form}}
{% for form in DirFormSet %}
{{form.text}}
{% if error in form.text.errors %}
{{error|escape}
{% endif %}
{% endfor %}
</div>
<button id="add-another">add another</button>
<input type="submit" />
</form>
{% endblock %}
As a side note, if I submit data on this form it gives the following error.
Error
Exception Type: MultiValueDictKeyError
Exception Value:"u'form-0-id'"
By default, modelformset_factory creates one extra form. If you don't want any extra forms, set extra=0.
Dirset = modelformset_factory(Direction, form=DirectionForm, extra=0)
The KeyError is because you have not included the form's id field in your template. You should have something like:
{% for form in dir_formset %}
{{ form.id }}
{{ form.text }}
...
{% endfor %}
Note that you should be passing the formset instance dir_formset when rendering the template, not the class DirFormSet. Your view should be something like
return render(request, "test/test.html", {'dir_formset': dir_formset})
then the template should be updated to use dir_formset instead of DirFormSet.

Why are read-only form fields in Django a bad idea?

I've been looking for a way to create a read-only form field and every article I've found on the subject comes with a statement that "this is a bad idea". Now for an individual form, I can understand that there are other ways to solve the problem, but using a read only form field in a modelformset seems like a completely natural idea.
Consider a teacher grade book application where the teacher would like to be able to enter all the students' (note the plural students) grades with a single SUBMIT. A modelformset could iterate over all the student-grades in such a way that the student name is read-only and the grade is the editable field. I like the power and convenience of the error checking and error reporting you get with a modelformset but leaving the student name editable in such a formset is crazy.
Since the expert django consensus is that read-only form fields are a bad idea, I was wondering what the standard django best practice is for the example student-grade example above?
The reason you don't want to do this is because someone can change your disabled field to enabled and then submit the form. You would have to change the save function as to not insert the "disabled" data.
The standard way to do this is to not put the name in an input, but to display it as text
<form>
<div>
<label>Name</label>
<p>Johnny Five</p>
</div>
<div>
....
This is not possible in django.
I say if you really trust your userbase to not "mess" with things then go for it, but if its a public facing website with possible sensitive data then stay away.
As far as I can see for your situation, this is the ideal answer:
https://stackoverflow.com/a/2242468/1004781
Ie, simply print the model variables in the template:
{{ form.instance.LastName }}
When using a disabled field, you also need to make sure it remains populated correctly if the form fails validation. Here's my method, which also takes care of malicious attempts to change the data submitted:
class MyForm(forms.Form):
MY_VALUE = 'SOMETHING'
myfield = forms.CharField(
initial=MY_VALUE,
widget=forms.TextInput(attrs={'disabled': 'disabled'})
def __init__(self, *args, **kwargs):
# If the form has been submitted, populate the disabled field
if 'data' in kwargs:
data = kwargs['data'].copy()
self.prefix = kwargs.get('prefix')
data[self.add_prefix('myfield')] = MY_VALUE
kwargs['data'] = data
super(MyForm, self).__init__(*args, **kwargs)
for student/grading example, I have come up with a solution, where students are non editable fields and grades can be edited and updated as required. something like this
I am combining students objects and formset for grades in grade_edit class in view.py using zip function.
def grade_edit(request, id):
student = student.objects.get(id=id)
grades = grades.objects.filter(studentId=id)
gradeformset = GradeFormSet(request.POST or None)
if request.POST:
gradeformset = GradeFormSet(request.POST, request.FILES, instance=student)
if gradeformset.is_valid():
gradeformset.save()
grades = grades.objects.filter(studentId=id)
return render(request, 'grade_details.html', {'student': student, 'grades': grades})
else:
gradeformset = GradeFormSet(instance=student)
grades = grades.objects.filter(studentId=id)
zips = zip(grades, gradeformset)
return render(request, 'grade_edit.html', {'zips': zips, 'student': student, 'gradeformset': gradeformset })
My template looks something like this
<table>
<tr>
{% for field in gradeformset.forms.0 %}
{% if not field.is_hidden %}
<th>{{ field.label }}</th>
{% endif %}
{% endfor %}
</tr>
{% for f in gradeformset.management_form %}
{{ f }}
{% endfor %}
{% for student, gradeform in zips %}
<tr>
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<td> {{ student.name }} </td>
<td> {{ gradeform.gradeA }} </td>
<td> {{ gradeform.gradeB }} </td>
</tr>
{% endfor %}
</table>
You can read more about Django formset here
http://whoisnicoleharris.com/2015/01/06/implementing-django-formsets.html