I'm trying to test an django ajax view that saves a field. Here's the test:
def test_ajax_save_draft(self):
self.client.force_login(self.test_user) # view requires login
sub = mommy.make(QuestSubmission, quest=self.quest2)
draft_comment = "Test draft comment"
# Send some draft data via the ajax view, which should save it.
ajax_data = {
'comment': draft_comment,
'submission_id': sub.id,
}
self.client.post(
reverse('quests:ajax_save_draft'),
data=ajax_data,
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
)
self.assertEqual(draft_comment, sub.draft_text) # sub.draft_text = None, as if the ajax view was never called!
And here's the view:
#login_required
def ajax_save_draft(request):
if request.is_ajax() and request.POST:
submission_comment = request.POST.get('comment')
submission_id = request.POST.get('submission_id')
sub = get_object_or_404(QuestSubmission, pk=submission_id)
sub.draft_text = submission_comment
sub.save()
response_data = {}
response_data['result'] = 'Draft saved'
return HttpResponse(
json.dumps(response_data),
content_type="application/json"
)
else:
raise Http404
When I run the test, I get into the if block, and it can retrieve the comment and submission object, but when it returns to the test at the end, it's like the save never happened.
What am I doing wrong here?
Try calling sub.refresh_from_db()
Changes made to a model instance will only affect the instance being modified. Any existing model instances will not be updated, refresh_from_db will get the changes from the database
Related
I have a Django function-based form view that initializes a form with default data on a GET request, and saves the model object on a POST request:
def copy(request, ann_id):
new_ann = get_object_or_404(Announcement, pk=ann_id)
new_ann.pk = None # autogen a new primary key (quest_id by default)
new_ann.title = "Copy of " + new_ann.title
new_ann.draft = True
new_ann.datetime_released = new_ann.datetime_released + timedelta(days=7)
form = AnnouncementForm(request.POST or None, instance=new_ann)
if form.is_valid():
new_announcement = form.save(commit=False)
new_announcement.author = request.user
new_announcement.datetime_created = timezone.now()
new_announcement.save()
form.save()
return redirect(new_announcement)
context = {
"title": "",
"heading": "Copy an Announcement",
"form": form,
"submit_btn_value": "Create",
}
return render(request, "announcements/form.html", context)
I can't figure out how to test the form.is_valid() branch when the form is posted, without manually providing the form data to self.client.post(url, form_data) in my view.
Here' what I'm trying:
test_views.py
class AnnouncementViewTests(TestCase):
def setUp(self):
self.client = ...
... etc
def test_copy_announcement(self):
# log in a teacher
success = self.client.login(username=self.test_teacher.username, password=self.test_password)
self.assertTrue(success)
# hit the view as a get request first, to load a copy of the announcement in the form
response = self.client.get(
reverse('announcements:copy', args=[self.test_announcement.id]),
)
self.assertEqual(response.status_code, 200)
# The form in this response should be valid, and should bw
# accepted on a post request,
# that is what I am testing in this integration test.
form_data = response.how_do_get_the_form_data() # ???????
response = self.client.post(
reverse('announcements:copy', args=[self.test_announcement.id]),
data=form_data
)
# Get the newest announcement just made in the post request
new_ann = Announcement.objects.latest('datetime_created')
self.assertRedirects(
response,
new_ann.get_absolute_url()
)
What I want to actually test is that the result of the get provides valid default data for the form that can then be submitted via post request.
But I can't figure out how to access the form data resulting from the get request, so I can then feed it into the form_data provided to the post request.
EDIT
I found the location of the form in the get response, but I have no idea how to get that in code.
You can access the response form in this way:
response.context['form']
From here you can build your payload in this way:
retrieved_instance = response.context['form'].instance
form_data = dict(title=retrieved_instance.title, ... <all the other fields>)
response = self.client.post(
reverse('announcements:copy', args=[self.test_announcement.id]),
data=form_data)
)
This is not like resubmitting the page but is very similar because you're resubmitting the same form.
Actually, your test seems more like an e2e test (when speaking about integrations and e2e there is some ambiguity let's say), for this reason, if I were you, I would switch "tool" and use selenium to simulate a user interaction from the beginning (open an existing announcement) to the end, pressing the submit button on your web page.
Only in this way what you're submitting is the "real" response of the "get"
If you are new to this kind of test, you can find here a simple tutorial https://lincolnloop.com/blog/introduction-django-selenium-testing/ to understand the main concepts.
Your question is bit confusing but I will try to head you in right direction.
To clean-up the code you should use Class Based View where you can use your form easily anywhere. A sample code I just wrote:
class TestView(View):
template_name = 'index.html'
success_url = 'home' # dummy view
context = {"form": myform()}
# myform is the definition/class of your form which contains all attrs.
def get(self, request):
context = self.context
context['form'] = form # fill out your data here for get request
return render(request, self.template_name, context)
def post(self, request):
context=self.context
# self.context certain that you're using exact form which you defined in class-scope
form=context['form']
# Form Validation
if form.is_valid():
#perform any logical validations here and save the form if required
return redirect(self.success_url)
context = self.context
context['form'] = form # just to show you that you can access that form anywhere
return render(request, self.template_name, context)
You coul manually pass data to you form like this and test the is_valid function in a unit test:
form_data = {'my': 'value', 'form': 'value', 'fields': 'value'}
form = AnnouncementForm(form_data)
self.assertFalse(form.is_valid)
I'm trying to use Django to output an HTML page based on whether the session is set or not.
when I submit my Django Form (via my view) I set the session like this:
def index(request):
users = Users.objects.all()
totalUsers = len(users)
form = CreateUserForm(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
form = CreateUserForm()
context = {
"form": form,
'users': users,
"totalUsers": totalUsers,
}
request.session.set_expiry(300)
request.session['loggedIn'] = True
return render(request, 'SmartCity/index.html', context)
I know this is successful because I can see the value set in the DB.
In my CustomTags.py file, I want to more or less check the session variable "loggedIn" is set, and if it is, return one thing, otherwise, return something else. This is how I thought to achieve it, but it's not working:
from Django import template
register = template.Library()
#register.inclusion_tag('SmartCity/index.html', takes_context=True)
def hello_world(context):
request = context['request']
loggedInStatus = request.session.get('logged_in', 'False')
if loggedInStatus == True:
return "Hello world"
The error I receive is:
https://preview.ibb.co/dqAe8k/2017_09_19_18_06_57.png
I could totally be on the wrong track... I would appreciate any advice you might be able to give a Django beginner :)
I'm trying to write tests for an Admin action in the change_list view. I referred to this question but couldn't get the test to work. Here's my code and issue:
class StatusChangeTestCase(TestCase):
"""
Test case for batch changing 'status' to 'Show' or 'Hide'
"""
def setUp(self):
self.categories = factories.CategoryFactory.create_batch(5)
def test_status_hide(self):
"""
Test changing all Category instances to 'Hide'
"""
# Set Queryset to be hidden
to_be_hidden = models.Category.objects.values_list('pk', flat=True)
# Set POST data to be passed to changelist url
data = {
'action': 'change_to_hide',
'_selected_action': to_be_hidden
}
# Set change_url
change_url = self.reverse('admin:product_category_changelist')
# POST data to change_url
response = self.post(change_url, data, follow=True)
self.assertEqual(
models.Category.objects.filter(status='show').count(), 0
)
def tearDown(self):
models.Category.objects.all().delete()
I tried using print to see what the response was and this is what I got:
<HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/admin/login/?next=/admin/product/category/">
It seems like it needs my login credentials - I tried to create a user in setUp() and log in as per Django docs on testing but it didn't seem to work.
Any help would be appreciated!
I found the solution - I wasn't instantiating Django's Client() class when I created a superuser, so whenever I logged in - it didn't persist in my subsequent requests. The correct code should look like this.
def test_status_hide(self):
"""
Test changing all Category instances to 'Hide'
"""
# Create user
user = User.objects.create_superuser(
username='new_user', email='test#example.com', password='password',
)
# Log in
self.client = Client()
self.client.login(username='new_user', password='password')
# Set Queryset to be hidden
to_be_hidden = models.Category.objects.values_list('pk', flat=True)
# Set POST data to be passed to changelist url
data = {
'action': 'change_to_hide',
'_selected_action': to_be_hidden
}
# Set change_url
change_url = self.reverse('admin:product_category_changelist')
# POST data to change_url
response = self.client.post(change_url, data, follow=True)
self.assertEqual(
models.Category.objects.filter(status='show').count(), 0
)
I wrote simple plugin to add comments onto page using form.
Here is the plugin code:
class KomentarzePlugin(CMSPluginBase):
model = CMSPlugin
name = _("Komentarze plugin")
render_template = "komentarze/komentarze_wtyczka.html"
def render(self, context, instance, placeholder):
request = context['request']
print 'weszlo1'
print request.method
if request.method == 'POST':
form = KomentarzForm(request.POST)
print 'weszlo2'
if form.is_valid():
user = form.cleaned_data['user']
tresc = form.cleaned_data['tresc']
strona = request.current_page
data = timezone.datetime.now()
k = Komentarz(autor=user, data=data, tresc=tresc, strona=strona)
k.save()
context.update({
'instance': instance,
'placeholder': placeholder,
'komentarze': Komentarz.objects.all().filter(strona=request.current_page).order_by('-data'),
'forma': KomentarzForm()
})
return context
plugin_pool.register_plugin(KomentarzePlugin)
When I restart server, fill the form with data, hit submit, then if statement with POST method is satisfied and function enters it, post is added to database and shown. But, when I try to do it again, it don't even print request.method to console, which means it is empty. Restarting server fixes the problem. Also it doesn't work when I restart the server, do some random menu clicks, and then try to fill and send the form.
Any guess?
I finally solved my problem. In KomentarzePlugin class I added:
cache = False
My views.py:
#login_required
def some_views(request):
if request.method == 'POST':
form = AddressCreateFrom(request.POST)
if form.is_valid():
name = form.cleaned_data['Address']
ip_value = form.cleaned_data['value']
user_list = get_username(name)
address_create = form.save()
extra_context = {
'user_list': user_list
}
return redirect_to(request, url=address_create.get_absolute_url())
else:
form = AddressCreateFrom()
extra_context = {
'form':AddressCreateFrom(initial={'user': request.user.pk})
}
return direct_to_template(request,'networks/user_form.html',extra_context)
In form.py:
class AddressCreateFrom(forms.ModelForm):
Address = forms.CharField(max_length=40)
value = forms.CharField(max_length=40)
class Meta:
model = Network
widgets = {
'user': forms.HiddenInput()
}
As you see that i am using Django model form with two extra Django form field i.e. Address and value in AddressCreateForm class. I need all of the field at the time of rendering the template.
Indeed some_views method are working fine but i also want render some extra data written in context_dictionary i.e. user_list to a requesting URL i.e. address_create.get_absolute_url().
If i am not wrong, if we are handling with the database we have to use redirect_to method. Is it possible to do that?
A redirect will return a HTTP response with status code 301 or 302, and the location to redirect to:
301 MOVED PERMANENTLY
Location: http://www.example.com/new-url/
There is no template rendered by the original view, so you can't pass extra_context to it.
The user's browser will usually follow the redirect, and request the new URL.
If you want to display information about a particular user in the next view, you have to do something like:
design your URL pattern to include the user id, e.g. /users/200/,
include it as a get parameter e.g. /users/?id=200, then fetch the user id from request.GET in the view.
store the user_id in the session
Before redirecting, create a message using the messages framework using the user data.
Then in the view that you redirect to, you can fetch the user from the database, and add it to the template context.
Context, Extra Context and POST Data will not survive the redirect.
Here is what you can do.
# before the redirect
....
request.session['user_list'] = user_list
return redirect_to(request, url=address_create.get_absolute_url())
# after the redirect (in the views.py that handles your redirect)
....
user_list = request.session['user_list']
extra_context = { 'user_list': user_list }
....
# now you have the user_list in the extra_context and can send it to the rendering engine.
Note: This solution only works for redirects within your own server.