This question already has an answer here:
Why doesn't save() automatically call save_m2m()?
(1 answer)
Closed 6 years ago.
I'm trying to create a Debt model using Django. It has a debtors field which link a debt to many users.
It behaves the way I want through Django's administration panel, but I can't get it to work using a django form. I can create debts but the the debtors field is emptying, despite the browser showing an accurate list of users to choose from.
What's even more surprising is that I thought that using blank=False in the model definition shouldn't allow that.
models.py:
from django.db import models
from django.utils import timezone
class Debt(models.Model):
author = models.ForeignKey('auth.User', related_name='author')
name = models.CharField(max_length=200)
amount = models.FloatField()
debtors = models.ManyToManyField('auth.User', blank=False)
invoice = models.FileField(blank=True)
created_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.name
forms.py:
from django import forms
from .models import Debt
class NewDebtForm(forms.ModelForm):
class Meta:
model = Debt
fields = ('name', 'amount', 'invoice', 'created_date', 'debtors')
views.py:
from django.shortcuts import render, redirect
from .models import Debt
from .forms import NewDebtForm
def debt_list(request):
if request.method == "POST":
form = NewDebtForm(request.POST)
if form.is_valid():
debt = form.save(commit=False)
debt.author = request.user
debt.save()
debts = Debt.objects.order_by('created_date')
form = NewDebtForm()
return render(request, 'debt_share/debt_list.html',
{'debts': debts,
'form': form})
debt_list.html:
{% extends 'debt_share/base.html' %}
{% block content %}
{% for debt in debts %}
<h1>{{ debt.name }}</h1>
<h2>payƩ par {{ debt.author }}</h2>
<h2>{{ debt.amount }} {{ debt.invoice }}</h2>
<p>
Concerne
{% for user in debt.debtors.all %}
{{ user }}
{% endfor %}
</p>
{% endfor %}
<form method="POST">{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save</button>
</form>
{% endblock content %}
When using ModelForms for many-to-many relationships, and use form.save(commit=False), you have to also call form.save_m2m(). Many-to-many relationships have to be saved in a second step, after the original object has been created; otherwise, its ID would not be known.
For more details, refer to the explanation in the docs: https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/#the-save-method
Related
models.py
from django.db import models
# Create your models here.
class Subscriber(models.Model):
"""A subscriber Model"""
email = models.CharField(max_length=255, blank=False, null=False, help_text="Subscriber Email Address", unique=True)
full_name = models.CharField(max_length=100, blank=False, null=False, help_text="First and Last Name")
class Meta:
verbose_name = "Subscriber"
verbose_name_plural = "Subscribers"
forms.py
from django.forms import ModelForm
from .models import Subscriber
class SubscriberForm(ModelForm):
class Meta:
model = Subscriber
fields = ["email", "full_name"]
views.py
from django.shortcuts import render
from .forms import SubscriberForm
from django.http import HttpResponseRedirect
from django.contrib import messages
# Create your views here.
def subscriber(request):
if request.method == "POST":
subscriber_form = SubscriberForm(request.POST or None)
if subscriber_form.is_valid():
subscriber_form.save()
messages.success(request, "")
return HttpResponseRedirect("/")
else:
subscriber_form = SubscriberForm()
context = {
"form_subscriber": subscriber_form
}
return render(request, "subscriber/subscriber_form.html", context)
subscriber_form.html
{% block content %}
<div>
<form method="POST">
{% csrf_token %}
{{ subscriber_form.as_ul }}
<input type="submit" value="Submit">
</form>
</div>
{% endblock %}
Only my submit button is publishing, however the form is never showing up for me.
I have followed the django docs exactly and still am not getting any good results.
It should be form_subscriber not subscriber_form so:
{% block content %}
<div>
<form method="POST">
{% csrf_token %}
{{ form_subscriber.as_ul }}
<input type="submit" value="Submit">
</form>
</div>
{% endblock %}
Additionally, I'd recommend you to only use SubscriberForm(request.POST) in views without using None for GET request as it is already being handled in else condition so:
views.py:
def subscriber(request):
if request.method == "POST":
subscriber_form = SubscriberForm(request.POST)
...
I was trying to display a list of items on the front end of a project using Django. I successfully did it and could see the objects in a list upon spinning up the server. But as soon as I used context_object_name in the views.py file, and then check the page, the objects are not appearing. Also, there is no error shown in the terminal and the docker logs too.
Here's my Views.py file
from django.shortcuts import render
from django.views.generic import ListView
from .models import Book
# Create your views here.
class BookListView(ListView):
model = Book
context_object_name = 'book_list'
template_name = 'books/book_list.html'
Here's the models.py file
from django.db import models
# Create your models here.
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=200)
price = models.DecimalField(max_digits=6, decimal_places=2)
def __str__(self):
return self.title
Here's the book_list.html file
<!--templates/books/book_list.html-->
{% extends '_base.html' %}
{% block title %}Books{% endblock title %}
{% block content %}
{% for book in book_list %}
<div>
<h2>{{ object.title }}</h2>
</div>
{% endfor %}
{% endblock content %}
small mistake in book_list.html. object.title to replace book.title
<h2>{{ book.title }}</h2>
Your help will be nice for me. Here are that codes:
models.py:
from django.db import models
class TagModel(models.Model):
tag = models.CharField(max_length=50)
def __str__(self):
return self.tag
class MyModel(models.Model):
title = models.CharField(max_length=50)
tag = models.ManyToManyField(TagModel)
forms.py:
from django import forms
from .models import *
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = '__all__'
views.py:
from django.shortcuts import render, get_object_or_404, redirect
from .models import *
from .forms import *
def MyWriteView(request):
if request.method == "POST":
mywriteform = MyForm(request.POST)
if mywriteform.is_valid():
confirmform = mywriteform.save(commit=False)
confirmform.save()
return redirect('MyDetail', pk=confirmform.pk)
else:
mywriteform = MyForm()
return render(request, 'form.html', {'mywriteform': mywriteform})
form.html(1st trial):
<form method="post">
{% csrf_token %}
{{ mywriteform }}
<button type="submit">Save</button>
</form>
form.html(2nd trial):
<form method="post">
{% csrf_token %}
{{ mywriteform.title }}
<select name="tags" required="" id="id_tags" multiple="">
{% for taglist in mywriteform.tags %}
<option value="{{taglist.id}}">{{taglist}}</option>
{% endfor %}
</select>
<button type="submit">Save</button>
</form>
I am trying to add tags on my post. I made a simple manytomany tagging blog but it does not work. I submitted a post by clicking the save button, and the title was saved, but the tag was not. In the admin, it worked well.
Thank you in advance.
update the code like this
if mywriteform.is_valid():
confirmform = mywriteform.save(commit=False)
confirmform.save()
mywriteform.save_m2m()
return redirect('MyDetail', pk=confirmform.pk)
for more details Refer here
If I have models like this:
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
publisher = models.ForeignKey('Publisher')
title = models.CharField(max_length=255)
class BookImage(models.Model):
book = models.ForeignKey('Book')
file = models.ImageField(max_length=255)
title = models.CharField(max_length=255)
I want to make a page that:
Lists all the Books for a particular publisher (e.g. Book.objects.filter(publisher=34)).
For each Book, displays any existing BookImages.
For each Book displays 3 forms for uploading and titling new BookImages.
One submit button.
I don't need to edit the details of the Books - the forms are only for BookImages.
I'm getting in a tangle with modelformset_factory and inlineformset_factory and none of it is right... I feel like I'm making things too complicated. Any ideas?
Update:
Here are some things I've tried that head in a sort-of-right direction, but I'm not sure they help:
# Forms for multiple Books for this Publisher
# But I don't think I need forms for the Books in my situation?
my_publisher = Publisher.objects.get(pk=37)
BookFormSet = modelformset_factory(Book, fields=(['title']))
formset = BookFormSet(queryset=Book.objects.filter(publisher=my_publisher))
# Multiple BookImages on one Book:
# Good, but how do I do this for all of a Publisher's Books, and display existing BookImages?
my_book = Book.objects.get(pk=42)
BookImageFormSet = inlineformset_factory(Book, BookImage, fields=('file', 'title'))
formset = BookImageFormSet(instance=my_book)
I found an example of how to do this in this blog post. Below I've rewritten the example using my Publisher/Book/BookImage models, and generic class-based views, for future reference.
The form also allows the user to edit the titles of the Books, which wasn't what I originally wanted, but this seems easier than not doing it; the inline Book forms require at least one field each, so we may as well include the Book's title.
Also, to see how this worked, I've put together a small Django project using this code, and a little more detail, available on GitHub.
models.py:
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
title = models.CharField(max_length=255)
publisher = models.ForeignKey('Publisher', on_delete=models.CASCADE)
class BookImage(models.Model):
book = models.ForeignKey('Book', on_delete=models.CASCADE)
image = models.ImageField(max_length=255)
alt_text = models.CharField(max_length=255)
forms.py:
from django.forms.models import BaseInlineFormSet, inlineformset_factory
from .models import Publisher, Book, BookImage
# The formset for editing the BookImages that belong to a Book.
BookImageFormset = inlineformset_factory(
Book,
BookImage,
fields=('image', 'alt_text')),
extra=1)
class BaseBooksWithImagesFormset(BaseInlineFormSet):
"""
The base formset for editing Books belonging to a Publisher, and the
BookImages belonging to those Books.
"""
def add_fields(self, form, index):
super().add_fields(form, index)
# Save the formset for a Book's Images in a custom `nested` property.
form.nested = BookImageFormset(
instance=form.instance,
data=form.data if form.is_bound else None,
files=form.files if form.is_bound else None,
prefix='bookimage-%s-%s' % (
form.prefix,
BookImageFormset.get_default_prefix()),
)
def is_valid(self):
"Also validate the `nested` formsets."
result = super().is_valid()
if self.is_bound:
for form in self.forms:
if hasattr(form, 'nested'):
result = result and form.nested.is_valid()
return result
def save(self, commit=True):
"Also save the `nested` formsets."
result = super().save(commit=commit)
for form in self.forms:
if hasattr(form, 'nested'):
if not self._should_delete_form(form):
form.nested.save(commit=commit)
return result
# This is the formset for the Books belonging to a Publisher and the
# BookImages belonging to those Books.
PublisherBooksWithImagesFormset = inlineformset_factory(
Publisher,
Book,
formset=BaseBooksWithImagesFormset,
fields=('title',),
extra=1)
views.py:
from django.http import HttpResponseRedirect
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
from .forms import PublisherBooksWithImagesFormset
from .models import Publisher, Book, BookImage
class PublisherUpdateView(SingleObjectMixin, FormView):
model = Publisher
success_url = 'publishers/updated/'
template_name = 'publisher_update.html'
def get(self, request, *args, **kwargs):
# The Publisher whose Books we're editing:
self.object = self.get_object(queryset=Publisher.objects.all())
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
# The Publisher whose Books we're editing:
self.object = self.get_object(queryset=Publisher.objects.all())
return super().post(request, *args, **kwargs)
def get_form(self, form_class=None):
"Use our formset of formsets, and pass in the Publisher object."
return PublisherBooksWithImagesFormset(
**self.get_form_kwargs(), instance=self.object)
def form_valid(self, form):
form.save()
return HttpResponseRedirect(self.get_success_url())
templates/publisher_update.html:
{% extends 'base.html' %}
{% block content %}
<form action="" method="post" enctype="multipart/form-data">
{% for hidden_field in form.hidden_fields %}
{{ hidden_field.errors }}
{{ hidden_field }}
{% endfor %}
{% csrf_token %}
{{ form.management_form }}
{{ form.non_form_errors }}
{% for book_form in form.forms %}
{# Output a Book form. #}
{% for hidden_field in book_form.hidden_fields %}
{{ hidden_field.errors }}
{% endfor %}
<table>
{{ book_form.as_table }}
</table>
{# Check if our `nested` property exists, with BookImage forms in it. #}
{% if book_form.nested %}
{{ book_form.nested.management_form }}
{{ book_form.nested.non_form_errors }}
{% for bookimage_form in book_form.nested.forms %}
{# Output the BookImage forms for this Book. #}
{% for hidden_field in bookimage_form.hidden_fields %}
{{ hidden_field.errors }}
{% endfor %}
<table>
{{ bookimage_form.as_table }}
</table>
{% endfor %}
{% endif %}
{% endfor %}
<input type="submit" value="Update books">
</form>
{% endblock content %}
All formset_factory methods require a form which they can generate multiple times. So, you need to create a form (since you are using Models, you need to create a model form) for BookImage.
forms.py
class BookImageForm(ModelForm):
class Meta:
model = BookImage
# book field wont be generated in the template if it is excluded
exclude = ['book',]
views.py
from django.forms.formsets import formset_factory
def your_view(request):
# I'm just including the formset code which you need, im assuming you have the remaining code in working condition
# TO HANDLE NORMAL RENDERING OF FORM WHEN USER OPENS THE WEBPAGE
if request.method == "GET":
bookimage_form = BookImageForm()
bookimage_formset = formset_factory(bookimage_form, max_num=3)
return render(request, 'index.html', {'bookimage_formset': bookimage_formset})
# WHEN USER SUBMITS THE FORM
if request.method == "POST"
# Consider BookImageFormSet as a placeholder which will be able to contain the formset which will come through POST
BookImageFormSet = modelformset_factory(BookImage, BookImageForm, max_num=3)
# bookimage_formset is a variable which stores the Formset of the type BookImageFormSet which in turn is populated by the data received from POST
bookimage_formset = BookImageFormSet(request.POST)
# HIDDEN AUTO GENERATED FIELDS ARE CREATED WHEN THE FORMSET IS RENDERED IN A TEMPLATE, THE FOLLOWING VALIDATION CHECKS IF THE VALUE OF THE HIDDEN FIELDS ARE OKAY OR NOT
if bookimage_formset.is_valid():
# EACH FORM HAS TO BE INDIVIDUALLY VALIDATED, THIS IS NORMAL FORM VALIDATION. ONLY DIFFERENCE IS THAT THIS IS INSIDE A FOR LOOP
for bookimage_form in bookimage_formset:
if bookimage_form.is_valid:
bookimage_form.save()
return HttpResponse("Form saved!")
return HttpResponseRedirect('/')
PS: You can get the data from request.POST for other models in the same view to handle other data (such as the data for Books)
I am trying to create a Django app that allows a user to select a file name from a drop down menu and upload the file. Regardless of what the user has named their file, I will receive that file saved as whatever they selected on the drop down plus the date. I cannot seem to get my drop downs to show. Hours of reading the documentation, multiple stack overflow posts, and trial and error have not helped. * Does anyone know how to get the user-selected drop down value from views.py to models.py to be inserted into the uploaded file's path? *
On another note, I am sure there are other issues with my code, being a Django newb, so please point them out if you see them.
models.py
from django.db import models
from datetime import datetime
import os
def update_filename(instance, filename):
path = "documents/"
format = '{0}_{1}.csv'.format(vc, datetime.today().strftime('%Y%m%d'))
#note that vc is defined in views.py but I am not sure how to transfer this to models.py
#*************************************************************
return os.path.join(path, format)
class Document(models.Model):
docfile = models.FileField(upload_to= update_filename) #/%Y/%m/%d')
#value_chains = (('coffee', 'coffee'),('tea', 'tea'),)
dropdown = models.CharField(max_length=20, choices=(('coffee', 'coffee'),('tea', 'tea'),))
forms.py
from django import forms
from .models import Document
def validate_file_extension(value):
if not value.name.endswith('.csv'):
raise forms.ValidationError("Only CSV file is accepted")
class DocumentForm(forms.ModelForm):
docfile = forms.FileField(label='Select a file', validators=[validate_file_extension])
value_chains = (('coffee', 'coffee'),('tea', 'tea'),)
dropdown = forms.ChoiceField(choices=value_chains, required=True)
class Meta:
model = Document
fields = ['dropdown']
views.py
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views.generic.edit import CreateView
from .models import Document
from .forms import DocumentForm
def list(request):
# Handle file upload
if request.method == 'POST':
#bind the data to the form
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
#**** vc is defined here ************************************
vc = form.cleaned_data['dropdown']
newdoc = Document(docfile = request.FILES['docfile'])
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('file_upload_app.views.list'))
# if a GET (or any other method) we'll create a blank form:
else:
form = DocumentForm() # A empty, unbound form. This is what we can expect to happen the first time we visit the URL.
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'file_upload_app/list.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
list.html
<form action="{% url "list" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.dropdown }}
<p>
<h6>
Note: the uploaded file must be in
<b>.CSV</b>
format and contain a column labeled "A" which contains
<em>only</em>
numbers.
</h6>
</p>
<p>{{ form.non_field_errors }}</p>
<p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
<p>
{{ form.docfile.errors }}
{{ form.docfile }}
</p>
<p><input type="submit" value="Upload"/></p>
</form>
<!-- List of uploaded documents -->
<p>
{% if documents %}
<div class="col-sm-12" align="center" >
<ul>
<p>Files on server:</p>
{% for document in documents %}
<li>{{ document.docfile.name }}</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="col-sm-12" align="center" >
<p>No documents.</p>
</div>
I recommend you use Djano's CreateView for your view. In your case you would have
Class UploadDocumentView(CreateView):
model = Document
When you click submit on your form, and you have the action pointing to the correct view, it will create a new Document with the fields you provided.
Your template should have this in it, the url should be the name you gave your url. I recommend against using "list" as your url because that is a special name in python and Django has it's own concept of ListView.
<form id="upload_document" method="POST" action="{% url 'document:upload' %}">
{{ form }}
<input class="btn btn-primary" id="upload_document_button" type="submit" value="Upload Document"/>
</form>
Sometimes seeing a working example is useful. The one below uses Django's UpdateView. The UpdateView will use the get_object method to determine which object in the database to modify. It knows what model to look at because it is set to model = UserProfile, and it knows what values to save because you set them in your form.
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
primary_key=True)
company_identifier = models.CharField(max_length=5, blank=False)
employee_id = models.CharField(max_length=20, null=True, blank=False)
forms.py
def get_company_choices():
company_list = [('','-------')]
companies = models.Company.objects.values_list('id', 'name').order_by('name')
company_list = list(chain(company_list, companies))
return company_list
class EmployerForm(forms.ModelForm):
employee_id_confirm = forms.CharField(label="Confirm Employee ID", required=True)
company_identifier = forms.ChoiceField(choices=get_company_choices, label="Company")
class Meta:
model = models.UserProfile
fields = ('company_identifier', 'employee_id')
labels = {
'employee_id': "Employee ID",
}
views.py
#method_decorator(login_required, name='dispatch')
class EmploymentUpdate(SuccessMessageMixin, UpdateView):
model = UserProfile
template_name = 'employee/update_employment.html'
form_class = forms.EmployerForm
success_url = reverse_lazy('employee:home')
success_message = "Employment Updated."
def get_object(self):
return self.model.objects.get(pk=self.request.user.userprofile.pk)
I found the solution, denoted by the **** below:
models.py:
from django.db import models
from datetime import datetime
import os
value_chains = (
('coffee', 'coffee'),
('tea', 'tea'),
)
def update_filename(instance, filename):
path = "documents/"
#*************************************************************
#use instance to access the instance of Document's CharField function, which is defined below
format = '{0}_{1}.csv'.format(instance.dropdown, datetime.today().strftime('%Y%m%d'))
#*************************************************************
return os.path.join(path, format)
class Document(models.Model):
docfile = models.FileField(upload_to= update_filename)
dropdown = models.CharField(max_length=20, choices=value_chains)
class Meta:
permissions = (("access_file_upload_app", "Access file upload tool."),)
views.py:
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.conf import settings
from .models import Document
from .forms import DocumentForm
#login_required
def doc_upload_view(request):
if request.user.has_perm('file_upload_app.access_file_upload_app') and settings.VC_ENABLED == 'Y':
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
#********************************************************
# make sure to pass the dropdown value to the Document call:
newdoc = Document(docfile = request.FILES['docfile'], dropdown = form.cleaned_data['dropdown'])
#********************************************************
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('file_upload_app.views.doc_upload_view'))
else:
form = DocumentForm() # An empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'file_upload_app/doc_upload_view.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
else:
return redirect('/home/')
forms.py:
from django import forms
from .models import Document
def validate_file_extension(value):
if not value.name.endswith('.csv'):
raise forms.ValidationError("Only CSV file is accepted")
class DocumentForm(forms.ModelForm):
value_chains = (('coffee', 'coffee'),('tea', 'tea'),)
docfile = forms.FileField(label='Select a file', validators=[validate_file_extension])
dropdown = forms.ChoiceField(choices=value_chains, required=True )
class Meta:
model = Document
fields = ('dropdown',)
doc_upload_view.html:
<!-- Upload form. Note enctype attribute! -->
<form action="{% url "doc_upload_view" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.dropdown }}
<p>{{ form.non_field_errors }}</p>
<p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
<p>
{{ form.docfile.errors }}
{{ form.docfile }}
</p>
<p><input type="submit" value="Upload"/></p>
</form>
<!-- List of uploaded documents -->
<p class="top-space">
{% if documents %}
<div class="col-sm-12" align="center" >
<ul>
<p>Files on server:</p>
{% for document in documents %}
<li>{{ document.docfile.name }}</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="col-sm-12" align="center" >
<p>No documents.</p>
</div>
{% endif %}
</p>