In my Django app I have a form with an email and a text area that needs to go into the database but am struggeling to do so. At the moment I have 2 different solutions in my code:
OK, let me write this from the beginning. Maybe it is better if you do the below steps with me now for an exercise.
Now, the correct way to do the above task (I try to clear this subject a little more because I know that many other people will read this problem and they can learn from this too. And it is important to understand it for your future tasks.
If you want to create a Form and you know that you will want to save the submitted data from that Form to the database, then of course you should start the whole task with creating a Model, thus a table for that in the database.
So, first you create a Model (which you will call ie. “Questions” in this case, since you want to call your Form ie. QuestionForm, so it is better if your Model and table will be related to that with their names too).
So your Model will be in your models.py file:
from django.db import models
# Create your models here.
class Questions(models.Model):
contact_email = models.EmailField(max_length=60)
question = models.CharField(max_length=600)
Then you will create a ModelForm from this in your forms.py file the following way:
from django import forms
from django.forms import ModelForm, Textarea
from . import models
class QuestionForm(ModelForm):
class Meta:
model = models.Questions
fields = ['contact_email', 'question']
widgets = {
'question': Textarea(attrs={'cols': 40, 'rows': 20}),
}
Then you create your view function in your views.py file:
from django.shortcuts import render, redirect
from . import forms
from . import models
from django.http import HttpResponse
def get_question(request):
form = forms.QuestionForm()
if request.method == 'POST':
form = forms.QuestionForm(request.POST)
if form.is_valid():
form.save()
return redirect(‘success.html’) # or you redirect anywhere you want to
else:
form = forms.QuestionForm()
return render(request, 'contact.html', {'form':form})
And at this point you will create your urlpattern in your urls.py to call the get_question view function. It will look like the following:
from django.conf.urls import url
from basic_app import views
# app_name = 'basic_app' # Important for referencing urls in HTML pages(use your own app_name here). But for this example task this is not important.
urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^questions/', views.get_question, name="questions_page"),
]
I hope I did not confuse you more. If you do the above steps, it should work for you. And you can create as many Forms as you want with the above steps and saving them easily in the Database.
The only other thing you have to have to run the above, is your 'contact.html' page which you already have and you already created.
(do not forget to run: python manage.py migrate)
So, I hope that you see, in the above example you do not mismatch fields and field names, and you do not get confused about what to save where. Since the Model and the Form is working together and created together with the same field names.
Related
I want to practice testing on Django, and I have a CreateView I want to test. The view allows me to create a new post and I want to check if it can find posts without a publication date, but first I'm testing posts with published date just to get used to syntax. This is what I have:
import datetime
from django.test import TestCase
from django.utils import timezone
from django.urls import reverse
from .models import Post, Comment
# Create your tests here.
class PostListViewTest(TestCase):
def test_published_post(self):
post = self.client.post('/post/compose/', {'author':"manualvarado22", 'title': "Super Important Test", 'content':"This is really important.", 'published_date':timezone.now()})
response = self.client.get(reverse('blog:post_detail'))
self.assertContains(response, "really important")
But I get this:
django.urls.exceptions.NoReverseMatch: Reverse for 'post_detail' with no
arguments not found. 1 pattern(s) tried: ['post/(?P<pk>\\d+)/$']
How do I get the pk for that newly created post?
Thank you!
You can get it directly from the database.
Note, you shouldn't call two views in your test. Each test should only call the code it is actually testing, so this should be two separate views: one to call the create view and assert that the entry is in the db, and one that creates an entry directly and then calls the detail view to check that it displays. So:
def test_published_post(self):
self.client.post('/post/compose/', {'author':"manualvarado22", 'title': "Super Important Test", 'content':"This is really important.", 'published_date':timezone.now()})
self.assertEqual(Post.objects.last().title, "Super Important Test")
def test_display_post(self):
post = Post.objects.create(...whatever...)
response = self.client.get(reverse('blog:post_detail', pk=post.pk))
self.assertContains(response, "really important")
What you wanna do with testing is using the relyable Django Databse API for recevieng the created data and see if your view represents this data.
As you only create 1 model instance and save it. You may obtain its pk via
model_pk = Post.objects.get(author="manualvarado22").pk
This pk then should be inserted into your url as the Exception states.
But i also recommend abseconded test, where you directly check if the newly created "Post" exists in the DB via django model API.
Edit:
When testing, django or the test Module you are using, creates a clean database only for testing and destroys it after the test-run. So if you want to acces a User while testing you must have created the user in your Test setup or in the Test method itself. Otherwise the Usertable would be empty.
I was finally able to solve the issue thanks to your great answers as well as some extra SO research. This is how the test looks like:
def test_display_no_published_post(self):
test_user = User.objects.create(username="newuser", password="securetestpassword")
post = Post.objects.create(author=test_user, title="Super Important Test", content="This is really important.")
response = self.client.get(reverse('blog:post_detail', kwargs={'pk':post.pk}))
self.assertEqual(response.status_code, 404)
And this are the create and detail views:
class PostDetailView(DetailView):
model = Post
def get_queryset(self):
return Post.objects.filter(published_date__lte=timezone.now())
class PostCreateView(LoginRequiredMixin, CreateView):
login_url = '/login/'
redirect_field_name = 'blog/post_detail.html'
form_class = PostForm
model = Post
I've got a basic django-haystack SearchForm working OK, but now I'm trying to create a custom search form that includes a couple of extra fields to filter on.
I've followed the Haystack documentation on creating custom forms and views, but when I try to view the form I can only get the error:
ValueError at /search/calibration/
The view assetregister.views.calibration_search didn't return an HttpResponse object. It returned None instead.
Shouldn't basing this on SearchForm take care of returning a HttpResponse object?
forms.py
from django import forms
from haystack.forms import SearchForm
class CalibrationSearch(SearchForm):
calibration_due_before = forms.DateField(required=False)
calibration_due_after = forms.DateField(required=False)
def search(self):
#First we need to store SearchQuerySet recieved after / from any other processing that's going on
sqs = super(CalibrationSearch, self).search()
if not self.is_valid():
return self.no_query_found()
#check to see if any date filters used, if so apply filter
if self.cleaned_data['calibration_due_before']:
sqs = sqs.filter(calibration_date_next__lte=self.cleaned_data['calibration_due_before'])
if self.cleaned_data['calibration_due_after']:
sqs = sqs.filter(calibration_date_next__gte=self.cleaned_data['calibration_due_after'])
return sqs
views.py
from .forms import CalibrationSearch
from haystack.generic_views import SearchView
from haystack.query import SearchQuerySet
def calibration_search(SearchView):
template_name = 'search/search.html'
form_class = CalibrationSearch
queryset = SearchQuerySet().filter(requires_calibration=True)
def get_queryset(self):
queryset = super(calibration_search, self).get_queryset()
return queryset
urls.py
from django.conf.urls import include, url
from . import views
urlpatterns = [
....
url(r'^search/calibration/', views.calibration_search, name='calibration_search'),
....
]
Haystack's SearchView is a class based view, you have to call .as_view() class method when adding a urls entry.
url(r'^search/calibration/', views.calibration_search.as_view(), name='calibration_search'),
This helped me.
"removing the "page" prefix on the search.html template did the trick, and was a good temporary solution. However, it became a problem when it was time to paginate the results. So after looking around, the solution was to use the "page_obj" prefix instead of "page" and everything works as expected. It seems the issue is that the haystack-tutorial assumes the page object is called "page", while certain versions of django its called "page_obj"? I'm sure there is a better answer - I'm just reporting my limited findings."
See this: Django-Haystack returns no results in search form
I want that the landing page of my homepage is a form with an input and the user puts in stuff. So I followed a couple of tutorials and now I have this:
views.py:
def create2(request):
if request.method =='POST':
form = LocationForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('')
else:
form = LocationForm()
args = {}
args.update(csrf(request))
args['form'] = form
return render_to_response('location/index.html', args)
and in my urls.py:
url(r'^$', 'core.views.create2'),
which works perfectly fine, if I go to 127.0.0.1:8000 I get to index.html and when put in something in the input it gets saved in the database. However, the old part of my homepage looks like this
class LandingView(TemplateView):
model = Location
template_name="location/index.html"
def search
...
and the urls.py:
url(r'^$', core.views.LandingView.as_view(), name='index'),
which has a function search I So my question is, is there a way how I can merge the def create2 into my LandingView. I tried several things, but I am always ending up having the index.html without the input field. I also tried
def create2
...
def search
...
but didn't work.
Does anyone know how to merge that together?
EDIT
Thank you the working solution looks like this now
class Create(CreateView):
model = coremodels.Location
template_name = 'location/test.html'
fields = ['title']
def form_valid(self, form):
form.save()
return HttpResponseRedirect('')
return super(Create, self).form_valid(form)
Depending on the results you are looking for, there are multiple ways to solve this:
1. Use CreateView and UpdateView
Django already provides some classes that render a form for your model, submit it using POST, and re-render the form with errors if form validation was not successful.
Check the generic editing views documentation.
2. Override get_context_data
In LandingView, override TemplateView's get_context_data method, so that your context includes the form you are creating in create2.
3. Use FormView
If you still want to use your own defined form instead of the model form that CreateView and UpdateView generate for you, you can use FormView, which is pretty much the same as TemplateView except it also handles your form submission/errors automatically.
In any case, you can keep your search function inside the class-based view and call it from get_context_data to include its results in the template's context.
I'm using django generic views in my project CRUD. The CreateView class uses the following url to work:
urls.py
url(r'^create', BookCreate.as_view(model=Books, template_name='Myproj/book_create.html'), name='book_create'),
If I go the www.mywebsite.com/create the form appears just how I wanted it.
My problem is that I want to incorporate the form on another page, that already has a url, a view and a template. The url is like the one bellow:
urls.py
url(r'^author/(?P<id>[0-9]{1,})/$', author_view_handler, name='author_view'),
How can I resolve this?
The CreateView uses a ModelForm. If you want to use it also, you need to create a a Book model form yourself, something like this:
from django.forms import ModelForm
class BookModelForm(ModelForm):
pass
And then instantiate it form=BookModelForm() and pass it to the context of your author_view_handler view.
However I am not really sure why you would want to do something like that...
Update: To pass it to your view, use
from django.shortcuts import render
def author_view_handler(request):
form = BookModelForm()
return render(request, 'author_view_handler.html', {"form": form},
The above just passes the form to the author_view_handler view and does not contain any form handling code.
I want to build a generic delete view for my application. Basically I want to have the same behaviour as the standard django admin. E.g. I want to be able to delete different objects using the same view (and template).
I was looking on django docs, and looks like that DeleteViews are coupled with the models they are supposed to delete. E.g.
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
And I want to create something more generic, e.g.
class AnyDelete(DeleteView):
model = I want to have a list of models here
success_url = reverse_lazy('some-remaining-list')
The reason CBVs were invented were to solve problems like yours. As you have already written, to create your own delete view you just need to subclass DeleteView and change two properties. I find it very easy and do it all the time (the only non-dry work that I have to do is to hook it to urls.py).
In any case, if you really want to create something more generic (e.g only one view to delete every kind of model) then you'll need to use the content types framework. As you will see in the documentation, the content types framework can be used to work with objects of arbitrary models. So, in your case you can create a simple view that will get three parameters: app_label, model and pk of model to delete. And then you can implement it like this:
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404
def generic_delete_view(request, app_label, pk):
if request.method == 'POST':
my_type = ContentType.objects.get(app_label=app_label, model=model)
get_object_or_404(my_type.model_class(), pk=pk).delete()
# here you must determine *where* to return to
# probably by adding a class method to your Models
Of course in your urls.py you have to hook this view so that it receives three parameters (and then call it like this /generic_delete/application/Model/3). Here's an example of how you could hook it in your urls.py:
urlpatterns = patterns('',
# ....
url(
r'^generic_delete/(?P<app_label>\w+)/(?P<model>\w+)/(?P<pk>\d+)$',
views.generic_delete_view,
name='generic_delete'
) ,
# ...
)
If you have a list of objects and want to get the app_label and model of each one in order to construct the generic-delete urls you can do something like this:
from django.core.urlresolvers import reverse
object = # ...
ct = ContentType.objects.get_for_model(object)
generic_delete_url = reverse('generic_delete', kwargs = {
app_label=ct.app_label,
model=ct.model,
pk=object.pk
})
# so generic_delete_url now will be something like /my_app/MyModel/42