Django Generic Based view: not saving HTML form data to database - django

I have a Django library application with several books and authors, here is a /author/create html form used by the admin to create/update details of the author (firstname, lastname, dob, profile picture), referring the MDN Library application.
I have a Generic Class Based View for this purpose:
class AuthorCreate(PermissionRequiredMixin, CreateView):
permission_required = 'is_superuser'
model = Author
fields = '__all__'
success_url = reverse_lazy('author-detail')
class AuthorUpdate(PermissionRequiredMixin, UpdateView):
permission_required = 'is_superuser'
model = Author
fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death']
success_url = reverse_lazy('author-detail')
class AuthorDelete(PermissionRequiredMixin, DeleteView):
permission_required = 'is_superuser'
model = Author
success_url = reverse_lazy('authors')
And these are the url patterns:
urlpatterns += [
path('author/create/', views.AuthorCreate.as_view(), name='author_create'), # redirects to author_form.html
path('author/<int:pk>/update/', views.AuthorUpdate.as_view(), name='author_update'), # redirects to author_form.html
path('author/<int:pk>/delete/', views.AuthorDelete.as_view(), name='author_delete'), # redirects to author_confirm_delete.html
]
And this is the author_form.html for creating/updating author details:
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %}
//remaining code...
</form>
Now, on clicking the submit button in the html form above, it should redirect to author/id page(mentioned in the success_url), however the main concern is that a new author is not getting created in the first place.
I am not sure how the html form data is being saved, whether or not it is being saved in the first place, because the page is redirecting to the success_url.
Code Referred from MDN: https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms

In your Author model add,
def get_absolute_url(self):
return reverse('author_detail', kwargs={'pk': self.pk}) #add kwargs and app_name depending upon your requirement.
views.py
class AuthorCreate(PermissionRequiredMixin, CreateView):
permission_required = 'is_superuser'
model = Author
fields = '__all__'
class AuthorUpdate(PermissionRequiredMixin, UpdateView):
permission_required = 'is_superuser'
model = Author
fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death']
Other way if you are using form in CreateView and UpdateView,
def form_valid(self, form):
self.object = form.save()
# ....
return HttpResponseRedirect(self.get_success_url())
If you want to go to previous page only, then this SO question has an answer

Related

CreateView doesn't save object , throws 'this field is required ' error

models.py is :
class Todo(models.Model):
user=models.ForeignKey(User,on_delete=models.CASCADE,null=True,blank=True)
title=models.CharField(max_length=200)
desc=models.TextField(null=True,blank=True)
complete=models.BooleanField(default=False)
created=models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class Meta:
ordering = ['created']
views.py is:
class TaskCreate(generic.CreateView):
model = Todo
fields = '__all__'
template_name = 'create.html'
success_url = reverse_lazy('home')
create.html is:
<body>
go back
{{ form.as_p }}
<form method="post">
{% csrf_token %}
<input type="submit" value="submit">
</form>
</body>
Whenever I submit data from create.html form it doesn't save it to the database and throws this field is required on 'user' field. How do I resolve this?
You probably want to exclude the user field, since it is determined by the logged in user, so:
from django.conf import settings
class Todo(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, editable=False
)
# …
def __str__(self):
return self.title
class Meta:
ordering = ['created']
Then we inject the logged in user in the instance of the form:
from django.contrib.auth.mixins import LoginRequiredMixin
class TaskCreateView(LoginRequiredMixin, generic.CreateView):
model = Todo
fields = '__all__'
template_name = 'create.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
form.instance.user = request.user
return super().form_valid(form)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
Note: In Django, class-based views (CBV) often have a …View suffix, to avoid a clash with the model names.
Therefore you might consider renaming the view class to TaskCreateView, instead of TaskCreate.

FormView not saving data in Django

I am trying to allow users to save details of a workout for a specific exercise through submitting a form. My ExerciseDetailView displays the form how I'd like it to:
class ExerciseDetailView(DetailView):
model = Exercise
template_name = 'workouts/types.html'
def get_context_data(self, **kwargs):
context = super(ExerciseDetailView, self).get_context_data(**kwargs)
context['form'] = WorkoutModelForm
return context
But my problem is with saving the inputted data in the database. I have tried making both a FormView and a CreateView but am clearly missing something:
class ExerciseFormView(FormView):
form_class = WorkoutModelForm
success_url = 'workouts:exercise_detail'
def form_valid(self, form):
form.save()
return super(ExerciseFormView, self).form_valid(form)
Here is my referenced WorkoutModelForm:
class WorkoutModelForm(forms.ModelForm):
class Meta:
model = Workout
fields = ['weight', 'reps']
My template:
<form action="{% url 'workouts:workout' exercise.id %}" method="post">
{% csrf_token %}
{{ form }}
<button type="submit">Save</button>
</form>
Urls:
path('exercise/<int:pk>/detail/', ExerciseDetailView.as_view(), name='exercise_detail'),
path('exercise/<int:pk>/detail/', ExerciseFormView.as_view(), name='workout'),
And for context here is my Workout model which contains a get_absolute_url method:
class Workout(models.Model):
weight = models.FloatField(default=0)
reps = models.PositiveIntegerField(default=0)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE, default=None)
def get_absolute_url(self):
return reverse('exercise_detail', args=[str(self.pk)])
I am not receiving any errors, but when I submit the form my url remains the same, as I hoped, however the page just appears blank and the objects are not recorded. Can anybody please help me see what the problem is?
The problem is not your view, the Django logic will never trigger this view, the URLs are perfectly overlapping, so that means that for a URL, it will always trigger the first view (here the ExerciseDetailView), you should make the paths non-overlapping, for example with:
path('exercise/<int:pk>/detail/', ExerciseDetailView.as_view(), name='exercise_detail'),
path('exercise/<int:pk>/workout/', ExerciseFormView.as_view(), name='workout'),
Triggering the logic will however not be sufficient, since it will not link the Workout to the necessary exercise, you can alter the logic to:
from django.urls import reverse
class ExerciseFormView(CreateView):
form_class = WorkoutModelForm
def form_valid(self, form):
form.instance.exercise_id = self.kwargs['pk']
return super().form_valid(form)
def get_success_url(self):
return reverse('workouts:exercise_detail', kwargs={'pk': self.kwargs['pk']})
Need use CreateView
from django.views.generic.edit import CreateView
class ExerciseFormView(CreateView):
form_class = WorkoutModelForm
...

Django set ModelForm field without including it in the form

In my app, I have Users create Post objects. Each Post has a User
class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE)
...
I want to create a post-submission form for editing and submission, so I plan to use Django's ModelForm functionality.
class PostForm(ModelForm):
class Meta:
model = Post
fields = "__all__"
However, if I do this, then whoever is viewing the form will be able to set who the Post author is. I want to make sure that the resulting user field is them. But, if I exclude the user field from the ModelForm,
class PostForm(ModelForm):
class Meta:
model = Post
exclude = 'user'
then the user will not be set on form submission. I've hacked my way around this by making a custom form and updating the post field
def submit_view(request):
....
request.POST = request.POST.copy()
request.POST.update({
'user' : request.user.id
})
form = PostForm(request.POST, request.FILES)
....
but then I lose automatic UI generation and form validation, which in some ways defeats the purpose of the Form class. Could somebody point me to the idiomatic way of setting the user field without including it in the Form?
Try this view:
def submit_view(request):
form = PostForm(request.POST or None)
if form.is_valid():
new_post = form.save(commit=False)
new_post.user = request.user
new_post.save()
view.py
from django.views.generic import CreateView
from .models import Post
class PostCreate(CreateView):
model = Post
template_name ="new_Post_form.html"
fields = ['text']
def form_valid(self, form):
object = form.save(commit=False)
object.user = self.request.user
object.save()
return super(PostCreate, self).form_valid(form)
def get_success_url(self):
return "/"
url.py
url(r'^newpost$',views.PostCreate.as_view(),name='post_new',),
new_post_form.html
<form method="post" enctype="multipart/form-data" class="form" action="newpost" id="new-post-form">
<div class="modal-body">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</div>

Django form with fields from two different models

I need to display one form, with multiple fields from 2 different models.
Form will contain only part of fields from models, and layout will be made using the crispy forms.
My models:
class Company(BaseModel):
title = models.CharField(_('Company'), max_length=128)
domain = models.CharField(_('Domain'), max_length=128)
class Account(BaseModel):
company = models.ForeignKey(Company)
user = models.OneToOneField(User)
role = models.CharField(_('Role'), choices=ROLES, default='member', max_length=32)
Fields which I want to show in form:
company title, user first name, user last name, user email
Is it even possible? How can I do this?
The other answers on this page involve tossing away the benefits of model forms and possibly needing to duplicate some of the functionality you get for free.
The real key is to remember that one html form != one django form. You can have multiple forms wrapped in a single html form tag.
So you can just create two model forms and render them both in your template. Django will handle working out which POST parameters belong to each unless some field names clash - in which case give each form a unique prefix when you instantiate it.
Forms:
class CompanyForm(forms.ModelForm):
class Meta:
fields = [...]
model = Company
class AccountForm(forms.ModelForm):
class Meta:
fields = [...]
model = Account
View:
if request.method == 'POST':
company_form = CompanyForm(request.POST)
account_form = AccountForm(request.POST)
if company_form.is_valid() and account_form.is_valid():
company_form.save()
account_form.save()
return HttpResponseRedirect('/success')
else:
context = {
'company_form': company_form,
'account_form': account_form,
}
else:
context = {
'company_form': CompanyForm(),
'account_form': AccountForm(),
}
return TemplateResponse(request, 'your_template.html', context)
Template:
<form action="." method="POST">
{% csrf_token %}
{{ company_form.as_p }}
{{ account_form.as_p }}
<button type="submit">
</form>
In your forms.py
from django import forms
class YourForm(forms.Form):
title = forms.CharField()
first_name = forms.CharField()
last_name = ...
In your views.py
from forms import YourForm
from django import views
from models import Company, Account
class YourFormView(views.FormView)
template_name = 'some_template.html'
form_class = YourForm
success_url = '/thanks/'
def form_valid(self, form):
title = form.cleaned_data['title']
...
# do your processing here using Company and Account
# i.e. company = Company.objects.create(title=title, ...)
# account = Account.objects.get_or_create(
# user=..., company=company ...)
# ... more processing
#
# Call company.save() and account.save() after adding
# your processed details to the relevant instances
# and return a HttpResponseRedirect(self.success_url)
def is_valid(self):
# don't forget to validate your fields if need be here
As usual the docs are pretty helpful.
https://docs.djangoproject.com/en/1.7/topics/forms/

How do I use UpdateView?

I have two, presumably related, problems with UpdateView. First, it is not updating the user but creating a new user object. Second, I cannot restrict the fields displayed in the form.
Here is my views.py:
class RegistrationView(FormView):
form_class = RegistrationForm
template_name = "register.html"
success_url = "/accounts/profile/"
def form_valid(self, form):
if form.is_valid:
user = form.save()
user = authenticate(username=user.username, password=form.cleaned_data['password1'])
login(self.request, user)
return super(RegistrationView, self).form_valid(form) #I still have no idea what this is
class UserUpdate(UpdateView):
model = User
form_class = RegistrationForm
fields = ['username', 'first_name']
template_name = "update.html"
success_url = "/accounts/profile/"
and urls.py
url(r'^create/$', RegistrationView.as_view(), name="create-user"),
url(r'^profile/(?P<pk>\d+)/edit/$', UserUpdate.as_view(), name="user-update"),
How do I properly use UpdateView?
Problem 1.
The user is not being updated because you are using the same form
(RegistrationForm) to do your updates and to create new users.
Problem 2. Forms belong in a file of their own called forms.py.
My suggested refactoring:
#forms.py
#place(forms.py) this in the same directory as views.py
class UpdateForm(forms.ModelForm):
#form for updating users
#the field you want to use should already be defined in the model
#so no need to add them here again DRY
class Meta:
model = User
fields = ('field1', 'field2', 'field3',)
#views.py
#import your forms
from .forms import UpdateForm
#also import your CBVs
from django.views.generic import UpdateView
class UserUpdate(UpdateView):
context_object_name = 'variable_used_in `update.html`'
form_class = UpdateForm
template_name = 'update.html'
success_url = 'success_url'
#get object
def get_object(self, queryset=None):
return self.request.user
#override form_valid method
def form_valid(self, form):
#save cleaned post data
clean = form.cleaned_data
context = {}
self.object = context.save(clean)
return super(UserUpdate, self).form_valid(form)
slightly elegant urls.py
#urls.py
#i'm assuming login is required to perform edit function
#in that case, we don't need to pass the 'id' in the url.
#we can just get the user instance
url(
regex=r'^profile/edit$',
view= UserUpdate.as_view(),
name='user-update'
),
You left out a lot of info so not really sure what your setup is.My solution is based on the assumption that you have Django 1.5. You can learn more about handling forms with CBVs
first: user = form.save() saves in the db the form. since there's no pk in the form it creates a new one.
what you have to do is probably to check if a user with that username exists and if not create it (for this part check google).
second: to restrict field you have to specify them in the Meta class of the Form (which you didn't show here) check this https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#modelform.
If you are getting new objects in the database instead of updating existing ones then it is likely that you copied and pasted the template for new objects and forgot to change the form's action attribute. This should point to view that does the update either in the form of a hard-coded path or a URL tag ({% url '<name of URLconf>' object.id %).