I have created a ModelForm (Django 1.9.1) for my application settings page. It should work in the following manner:
Settings page should have multiple text fields with the value from database.
If I change any field and then press "Save" button - it should update same fields in DB, not add new.
Model:
class Settings(models.Model):
pkey_path = models.CharField(max_length=255)
class Meta:
db_table = "t_settings"
def __str__(self):
return self.id
Form:
class SettingsForm(ModelForm):
class Meta:
model = Settings
fields = ['pkey_path']
View:
def settings_update(request):
if request.method == "POST":
form = SettingsForm(request.POST)
if form.is_valid():
form.save()
return render(request, 't_settings/index.html', {'form': form})
else:
form = SettingsForm()
return render(request, 't_settings/index.html', {'form': form})
urls.py:
app_name = 't_settings'
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^update/', views.settings_update, name='settings-update'),
]
html form:
<form action="{% url 't_settings:settings-update' %}" method="post" class="form-horizontal">
{% csrf_token %}
<div class="box-body">
<div class="form-group">
<label for="{{ form.pkey_path.id_for_label }}" class="col-sm-3 control-label">test</label>
<div class="col-sm-8">{{ form.pkey_path.errors }}
<input type="text" class="form-control"
name="{{ form.pkey_path.name }}"
id="{{ form.pkey_path.id_for_label }}"
placeholder="Path"
value="{{ form.pkey_path.value }}">
I tried different approaches using Django docs, but anyway I get:
{{ form.pkey_path.value }} doesn't show the value from database in template
Form itself works, but adds new rows in database, instead of updating the existing ones
You're not doing anything in your view to either get an existing Settings entry to pass to the form, or tell the form which one to update on save. You need to have some way of identifying the object you want, for a start, which usually means accepting an id or slug in the URL; then you would need to query that object and pass it to the form with the instance parameter, both on initial instantiation and before saving.
You could override the model's save method to check if pkey_path already exists and overwrite existing record if so.
from django.core.exceptions import ObjectDoesNotExist
class Settings(models.Model):
pkey_path = models.CharField(max_length=255)
class Meta:
db_table = "t_settings"
def __str__(self):
return self.id
def save(self, *args, **kwargs):
try:
# will update existing record
self.pk = Settings.objects.get(pkey_path=self.pkey_path).pk
except ObjectDoesNotExist:
# if not existing record, will write a new one
self.pk = None
# call the original save method
super(Settings, self).save(*args,**kwargs)
Note that there will only be an instance associated w/ the form if successfully saved as your view lists now.
If this view is only to update existing records, you could do the following so there is always an instance:
View:
from django.shortcuts import get_object_or_404
from my_app.models import Settings
def settings_update(request, pkey_path=None):
instance = get_object_or_404(Settings,pkey_path=pkey_path)
if request.method == "POST":
form = SettingsForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return render(request, 't_settings/index.html', {'form': form})
else:
form = SettingsForm(instance=instance)
return render(request, 't_settings/index.html', {'form': form})
You must add an entry to urls.py which includes (?P<pkey_path>.+) for this view so that it fetches an existing Settings record. To add a new record, you could write a settings_add view or if no pkey_path supplied to the view make instance=Settings() to instantiate an empty instance of the Settings model.
EDIT #2: Handling a new record vs an existing record requires more than what you have in your view. Here's one way to do that.
In urls.py:
urlpatterns = patterns('',
url(r'^settings/$',views.settings_update, name='add-settings'),
url(r'^settings/(?P<pkey_path>.+)/$',views.settings_update, name='update-settings'),
# ...
)
In views.py:
from django.shortcuts import get_object_or_404
from my_app.models import Settings
def settings_update(request, pkey_path=None):
if pkey_path: # if supplied via url, find record w/ pkey_path. If pkey_path supplied matches no records, return 404.
instance = get_object_or_404(Settings,pkey_path=pkey_path)
else: # if pkey_path not supplied in url, create empty instance
instance = Settings() # instantiate new Settings object
if request.method == "POST": # if post submitted, save if valid
form = SettingsForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return render(request, 't_settings/index.html', {'form': form})
else: # no post submitted
form = SettingsForm(instance=instance)
return render(request, 't_settings/index.html', {'form': form})
Related
I'm familiar with creating forms based on Model forms, in that case I can use form.save() to save the data in database. The question is, how do I save/retrieve the data of this form that I didn't create through a model form?
I've tried the following code but I'm not sure how to retrieve and save the data from NameForm.
#forms.py file:
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label = 'your name', max_length = 100)
# views.py file
from django.shortcuts import render, redirect
from .forms import NameForm
def home(request):
return render(request, 'meal_plans/home.html')
def name(request):
if request.method != 'POST':
form = NameForm()
else:
form = NameForm(data=request.POST)
if form.is_valid():
your_name = form.cleaned_data['your_name']
return redirect('meal_plans:home')
return render(request, 'meal_plans/name.html', {'form': form})
# name.html template
<form action="{% url 'meal_plans:home' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form
One option is to save the data in the users session. The docs explain quite nicely how to implement this. In your case, it would be something like this:
if form.is_valid():
request.session['your_name'] = form.cleaned_data['your_name']
...
To access that session variable in another view, you would use:
your_name = request.session.get('your_name')
Possibly a newbie question, so please bear with me.
I have a Django form that edits a certain instance of a Model. I am using Modelforms. I am able to edit the instance but I am not able to see the content of instance that I want to edit.
I am learning django right now using video tutorials and in the tutorial adding instance=instance to ModelForm instance and then using form.as_p the values were populated in the input box.
In my case when I got to edit url my input fields are blank. However, whatever I write in new blank form gets updated to that object. What could have been wrong here? I am stuck at this point for 4 days so this question is a very desperate one :)
My form class:
from django import forms
from .models import Entry
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['name','type', 'date', 'description']
My Model:
from django.db import models
class Entry(models.Model):
name = models.CharField(max_length=200)
type = models.CharField(max_length= 200)
date = models.DateTimeField()
description = models.TextField()
My views look like this :
def update(request,pk):
instance = get_object_or_404(Entry,pk=pk)
if request.method == 'POST':
form = EntryForm(request.POST or None,instance=instance )
if form.is_valid():
instance =form.save(commit=False)
instance.save()
return HttpResponseRedirect('/')
else:
form = EntryForm()
return render(request, "form.html", {"name":instance.name,'instance':instance,'form': form})
Form template :
<form method="POST">
{% csrf_token %}
{{form.as_p}}
<button class="btn btn-success" type='submit'>Submit</button>
</form>
You are not passing the instance for the second case. Update your views.py to this.
def update(request,pk):
instance = get_object_or_404(Entry,pk=pk)
if request.method == 'POST':
form = EntryForm(request.POST or None,instance=instance )
if form.is_valid():
instance =form.save(commit=False)
instance.save()
return HttpResponseRedirect('/')
else:
form = EntryForm(instance=instance)
return render(request, "form.html", {"name":instance.name,'instance':instance,'form': form})
Django 2.0
Python 3.6
I am having trouble with a Django form that is not saving the file that is selected through the form; whenever you select a file to upload, I receive the message "This Field is Required.".
I placed a blank=True and a null=True in the Model FileField to get rid of the same, but whenever I attempt to load the html, I get this error: "The 'copydoc' attirbute has no file associated with it."
I would like for a user to be able to log in, create an entry and upload a file along with said entry. Why doesn't the DB accept the file from the form?
Thank you.
views.py:
from django.shortcuts import render, redirect
from .models import notarizer, CustomUser, notarizerCreateForm
# from .forms import notarizerCreateForm
# Create your views here.
def home(request):
t = 'home.html'
return render(request, t)
def page1(request):
t = 'log1/page1.html'
if request.user.is_authenticated:
logger = notarizer.objects.filter(userziptie=request.user).order_by('-date')
return render(request, t, {'logger': logger})
else:
return redirect(home)
def create_entry(request):
createPath = 'log1/create_entry.html'
if request.method == 'POST':
if request.method == 'FILES':
form = notarizerCreateForm(request.POST, request.FILES)
if form.is_valid():
instance =notarizerCreateForm(
file_field=request.FILES['file']
)
instance.save()
else:
print(form.errors)
else:
form = notarizerCreateForm(request.POST)
if form.is_valid():
form.save()
else:
print(form.errors)
else:
form = notarizerCreateForm()
return render(request, createPath, {'form': form})
create_entry.html:
{% extends "base.html" %}
{% block placeholder1 %}
<div class="form-holder">
<form name="form" enctype="multipart/form-data" method="POST"
action="/create_entry/" >
{% csrf_token %}
{{ form.as_table }}
<input type="submit"/>
</form>
</div>
{% endblock %}
models.py:
from django.db import models
from users.models import CustomUser
from django.forms import ModelForm
# Create your models here.
class notarizer(models.Model):
date = models.DateField(auto_now_add=True)
docName = models.CharField(max_length=25, null=False)
describe = models.TextField(max_length=280)
signee = models.CharField(max_length=25, null=False)
signeeDets = models.TextField(max_length=280)
copydoc = models.FileField(upload_to='users/', blank=True, null=True)
userziptie = models.ForeignKey('users.CustomUser',
on_delete=models.DO_NOTHING, null=True)
def __str__(self):
return "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}".format(
self.pk,
self.date,
self.docName,
self.describe,
self.signee,
self.signeeDets,
self.userziptie
)
class notarizerCreateForm(ModelForm):
class Meta:
model = notarizer
fields = ['docName','describe','signee','signeeDets', 'copydoc']
There are some things that make the view workflow very weird:
you check request.method, first you check if it is a 'POST' which is a good idea, but then you check if it is 'FILES', there is no HTTP method named FILES, there are only GET, POST, PATCH, PUT, OPTIONS, etc.;
you call form.is_valid() which is again what should happen, but then you create a new Form, and only pass it a single parameter; and
in case of a POST you should not return a rendered page, but redirect to a GET page (for example showing the result). The workflow is typically Post-redirect-get, since if the user refreshes their browser, we do not want to make the same post again.
The workflow should look like:
def create_entry(request):
createPath = 'log1/create_entry.html'
if request.method == 'POST': # good, a post (but no FILES check!)
form = notarizerCreateForm(request.POST, request.FILES)
if form.is_valid():
instance = form.save()
else:
# you probably want to show the errors in that case to the user
print(form.errors)
# redirect to a page, for example the `page1 view
return redirect(page1)
else:
form = notarizerCreateForm()
return render(request, createPath, {'form': form})
I'm trying to put multiple account management forms on the one page with a TemplateView as follows:
class AccountManagement(TemplateView):
""" Generic view to display the account management template """
template_name = 'accountmanagement.html'
def get_context_data(self, **kwargs):
context = super(AccountManagement, self).get_context_data(**kwargs)
context['user'] = self.request.user
# pass unbound form instances to context
# if there aren't bound instances already there
if context.get('usercreate_form') is None:
context['usercreate_form'] = UserCreateForm()
if context.get('password_form') is None:
context['password_form'] = PasswordChangeForm()
return context
I'm handling UserCreation with a FormView (because this example is simplified; I also need some non-model data, and CreateView needs a ModelForm). This view processes the POST request, and is supposed to redirect to the TemplateView with a success message, or pass the invalid bound form back to the context so that the template can render the errors. Trouble is, it doesn't do the part in bold italics (obviously HttpResponseRedirect doesn't pass the context). Why? How can I get the bound form back into the TemplateView context here so that the form.errors will be available and the user doesn't have to retype the data?
class UserCreate(FormView):
"""
Generic view to create a User.
"""
form_class = UserCreateForm
http_method_names = ['post',]
success_url = reverse_lazy('accountmanagement')
failure_url = reverse_lazy('accountmanagement')
def form_valid(self, form):
#password1 == password2 as per UserCreateForm.clean()
try:
new_user = User.objects.create_user(
username=form.cleaned_data['email'],
first_name=form.cleaned_data['first_name'],
last_name=form.cleaned_data['last_name'],
email=form.cleaned_data['email'],
password=form.cleaned_data['password1']
)
new_user.save()
messages.success(self.request, new_user.username + str(_(": successfully saved.") ))
return HttpResponseRedirect(self.success_url)
except IntegrityError:
#duplicate username
messages.error(self.request, _("Duplicate email address."))
return HttpResponseRedirect(self.failure_url, {'usercreate_form': form})
def form_invalid(self, form):
messages.error(self.request, _("Unable to create user."))
return HttpResponseRedirect(self.failure_url, {'usercreate_form': form})
template:
<form method="post" action="{% url 'usercreate' %}">{% csrf_token %}
{{ usercreate_form.errors }}
{{ usercreate_form.as_p }}
<button type="submit">{% trans 'Save' %}</button>
Final related question: is the right way to put several related forms on the one page a TemplateView? And then process non-model forms using a POST-only FormView with a redirect back to the TemplateView? Or should I do this a different way?
I am trying to have a user input a task from the frontend and have that data instantiate a new model and add this new field in the database associated with their account. I have tried the following;
Profile HTML
<form id="taskitem_form" method="post" action="/">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
{{ field.errors }}
{{ field.help_text }}
{{ field }}
{% endfor %}
<input type="submit" name="submit" value="Add Task" class ="btn btn-primary" />
</form>
Model
class TaskItem(models.Model):
taskn = models.CharField(max_length = 400)
usern = models.ForeignKey(User)
def __str__(self):
return self.taskn
Views
def add_task(request):
# Get the context from the request.
#context = RequestContext(request)
# A HTTP POST?
if request.method == 'POST':
form = TaskItemForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
task = form.save(commit=False)
task.usern = request.user
task.save()
# we should redirect after data modifying
return redirect('/user/%s' %(request.user))
else:
# If the request was not a POST, display the form to enter details.
return render(request, 'profile.html', {'form': form})
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render(request, 'profile.html', {'form': form})
Forms
from django import forms
from bkmks.models import TaskItem
class TaskItemForm(forms.ModelForm):
taskn = forms.CharField(max_length = 300, help_text = "Please enter your task")
# An inline class to provide additional information on the form.
class Meta:
fields = ('taskn', 'usern' )
#This is the association between the model and the model form
model = TaskItem
Lot's of Changes needed to your code.
I'm posting a working version so that you can try.
Put profile.html file as bkmks/templates/bkmks/profile.html
Get it working. Customize later.
profile.html
<form id="taskitem_form" method="post" action="">
{% csrf_token %}
{{form}}
<input type="submit" name="submit" value="Add Task" class ="btn btn-primary" />
</form>
model as it is.
views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render_to_response, RequestContext, redirect
from .forms import TaskItemForm
#login_required
def add_task(request):
# Get the context from the request.
context = RequestContext(request)
# A HTTP POST?
if request.method == 'POST':
form = TaskItemForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# Save the new category to the database.
task = form.save(commit=False)
task.usern = request.user
task.save()
# Redirect to home (/)
return redirect('/')
else:
# The supplied form contained errors - just print them to the terminal.
print form.errors
else:
# If the request was not a POST, display the form to enter details.
form = TaskItemForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render_to_response('bkmks/profile.html', {'form': form}, context)
forms.py
class TaskItemForm(forms.ModelForm):
# task is changed to taskn
taskn = forms.CharField(max_length = 300, help_text = "Please enter your task")
# An inline class to provide additional information on the form.
class Meta:
fields = ('taskn',)
#This is the association between the model and the model form
model = TaskItem
If you get any error or data is not getting saved post here.
Going through Django tutorial will be an wise decision.
The below should do what you need. You really want to inherit 100% of everything from your model when you can. This insures all model validation trickles down to the form. I utilized verbose_name and help_text on the model to achieve this.
Models
from django.conf import settings
class TaskItem(models.Model):
taskn = models.CharField(
max_length=400,
verbose_name="task",
help_text="Please enter your task.",
)
usern = models.ForeignKey(
to=settings.AUTH_USER_MODEL,
related_name="tasks",
)
def __str__(self):
return self.taskn
For the forms, I have added a forms.HiddenInput widget to the user, assuming you want the user submitting the task to become the user.
Forms
from django import forms
from bkmks.models import TaskItem
class TaskItemForm(forms.ModelForm):
widgets = {
'user': forms.HiddenInput,
}
class Meta:
model = TaskItem
fields = ('taskn', 'usern')
I have used a CreateView to reduce code complexity, and overrode the form_valid to add the user instance to the form.
Views
from django.views.generic import CreateView
from bkmks.models import TaskItem
from bkmks.forms import TaskItemForm
class TaskCreateView(CreateView):
model = TaskItem
form_class = TaskItemForm
template_name = "path/to/template.html"
def form_valid(self, form):
form.instance.user = self.request.user
return super(TaskCreateView, self).form_valid(form)
Finally, in the template, we simply want to use {{ form }}. I see you are looking into bootstrap. I'll suggest django-crispy-forms for this, but that is beyond the scope of your question.
Template
<form id="taskitem_form" method="post" action="/">
{% csrf_token %}
{{ form }}
<input type="submit" name="submit" value="Add Task" class ="btn btn-primary" />
</form>
https://docs.djangoproject.com/en/1.8/topics/http/shortcuts/#render-to-response
render_to_response expects a template as the first argument, not a url.
I think in your second call to render_to_response should include the template name / path , while the first one should use a return HttpResponseRedirect("/") instead, though its not clear exactly what your problem is.
Add this line to imports in views.py
from django.contrib.auth.decorators import login_required
Decorate add_task view
#login_required
def add_task(request):
Then, edit part of your code
if form.is_valid():
task = form.save(commit=False)
task.usern = request.user
task.save()
# we should redirect after data modifying
return redirect('/')
else:
# etc.
Some notes. You may replace render_to_response to render.
Remove this line
context = RequestContext(request)
Replace
# Wrong usage, actually.
# Should be something like
# render_to_response(template_name, context, context_instance)
render_to_respone('/', {'form': form}, context)
with
# if template_name is "profile.html"
render(request, 'profile.html', {'form': form})
Why define a field called task in the form if you've already got a field in the model called taskn, wouldn't it be better to just use that? And like the guys have said, you need to specify a template to render (that's why you're not seeing anything).
It'd also be a good idea to pass the current user to the form's user field.
#login_required
def add_task(request):
# Get the context from the request.
context = {}
# A HTTP POST?
if request.method == 'POST':
form = TaskItemForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# Save the new category to the database.
form.save()
# Now call the index() view.
# The user will be shown the homepage.
return render_to_response(
'profile.html',
{'form': form},
RequestContext(request, context)
)
else:
# The supplied form contained errors - just print them to the terminal.
print form.errors
else:
# If the request was not a POST, display the form to enter details.
form = TaskItemForm(initial={'usern': request.user})
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render_to_response(
'profile.html',
{'form': form},
RequestContext(
request, context
)
)
Form;
from django import forms
from bkmks.models import TaskItem
class TaskItemForm(forms.ModelForm):
taskn = forms.CharField(max_length = 300, help_text = "Please enter your task")
# An inline class to provide additional information on the form.
class Meta:
fields = ('taskn', 'usern' )
#This is the association between the model and the model form
model = TaskItem