Django tests AssertionError for update view - django

I tried to create Django test for UpdateView
but I have such problem as:
self.assertEqual(application.credit_purpose, 'House Loan')
AssertionError: 'Car Loan' != 'House Loan'
Car Loan
House Loa
def test_application_update(self):
application = Application.objects.create(customer=self.customer, credit_amount=10000, credit_term=12,
credit_purpose='Car Loan', credit_pledge=self.pledge,
product=self.product,
number_request=2, date_posted='2020-01-01', reason='None',
repayment_source=self.repayment, possible_payment=1000,
date_refuse='2020-01-02', protokol_number='123457',
status=self.status,
language=0, day_of_payment=1, credit_user=self.user)
response = self.client.post(
reverse('application_update', kwargs={'pk': application.id}),
{'credit_purpose': 'House Loan'})
self.assertEqual(response.status_code, 200)
application.refresh_from_db()
self.assertEqual(application.credit_purpose, 'House Loan')
This is my model
class Application(AbstractCredit):
number_request = models.IntegerField(verbose_name='Номер заявки', unique=True, default=number_auto) # Добавить автоинкремент
date_posted = models.DateField(verbose_name='Дата заявки', auto_now_add=True)
reason = models.CharField(max_length=200, null=True, blank=True, verbose_name='Причина отказа/Условия одобрения')
repayment_source = models.ForeignKey(Repayment, on_delete=models.CASCADE, verbose_name='Источник погашения')
possible_payment = models.IntegerField(verbose_name='Желаемая сумма ежемесячного взноса')
date_refuse = models.DateField(default=one_day_more, null=True, blank=True, verbose_name='Дата отказа/одобрения')
protokol_number = models.CharField(max_length=20, unique=True, null=True, blank=True,
verbose_name='Номер протокола')
status = models.ForeignKey(Status, on_delete=models.CASCADE, default=1, verbose_name='Статус')
language = models.IntegerField(choices=LANGUAGES_CHOICES, verbose_name='Язык договора', blank=True, null=True)
day_of_payment = models.IntegerField(choices=DAY_OF_PAYMENT_CHOICES,
verbose_name='Предпочитаемый день оплаты по кредиту')
credit_user = models.ForeignKey(User, on_delete=models.SET(0), verbose_name='Кредитный специалист')
This is my view
class ApplicationUpdate(BasePermissionMixin, SuccessMessageMixin, UpdateView):
model = Application
form_class = ApplicationForm
template_name = 'standart_form.html'
permission_required = 'Изменение заявки'
success_message = 'Заявка успешно изменена'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['button_name'] = 'Сохранить'
return context
def get_success_url(self):
return reverse_lazy('application_detail', kwargs={'pk': self.get_object().id})

So, I also got stuck in this for a while. The main problem is at:
...
response = self.client.post(
reverse('application_update', kwargs={'pk': application.id}),
{'credit_purpose': 'House Loan'}
)
...
I was able to understand thanks to this answer. It happens because you are posting to a form and it expects to have all fields filled, otherwise it will fail validation and thus will not call .save().
What you can do is, after creating the object copy its data into a dictionary, then modify it before posting:
tests.py
from django.forms.models import model_to_dict
class ApplicationTestCase(TestCase):
def setUp(self):
customer = Customer.objects.create(...)
...
self.data = {
'customer': customer, 'credit_amount': 10000, 'credit_term': 12,
'credit_purpose': 'Car Loan', ...
}
def test_application_update(self):
application = Application.objects.create(**self.data)
post_data = model_to_dict(application)
post_data['credit_purpose'] = 'House Loan'
response = self.client.post(
reverse(
'app:view-name', kwargs={'pk': application.id}),
post_data
)
# print(application.credit_purpose)
# application.refresh_from_db()
# print(application.credit_purpose)
# It returns a redirect so code is 302 not 200.
self.assertEqual(response.status_code, 302)
self.assertEqual(application.credit_purpose, 'House Loan')
Also, get_absolute_url() is set in the wrong place should be under models:
models.py
class Application(AbstractCredit):
...
def get_absolute_url(self):
return reverse('app:view-name', kwargs={'pk': self.pk})

Related

How to write test for Django image url

I'm writing my first test as a Django developer.
How do I write a test for the image property of this model shown below
class Project(models.Model):
engineer = models.ForeignKey(Engineer, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
tech_used = models.CharField(max_length=200)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
image = models.ImageField(default="project.png")
description = models.TextField(null=True, blank=True)
repo_url = models.URLField(null=True, blank=True)
live_url = models.URLField(null=True, blank=True)
make_public = models.BooleanField(default=False)
def __str__(self):
return self.name
#property
def image_url(self):
try:
url = self.image.url
except:
url = ""
return url
class Meta:
ordering = ["-created"]
I have tried to conjure some patches from here and there although I don't fully understand what my test.py code I just thought to include it to my question to show my efforts
class TestModels(TestCase):
def setUp(self):
new_image = BytesIO()
self.image = Project.objects.create(
image=ImageFile(new_image)
)
def test_image_url(self):
self.assertEquals(self.image.image_url, '')
Please a help on the above question and an explanation of whatever code given as help will be highly appreciated.
Following this answer, one possible way is:
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.utils import timezone
from .models import Engineer, Project
class ProjectImageUploadTest(TestCase):
def setUp(self):
self.engineer = Engineer.objects.create(name='Engineer')
self.project = {
'engineer': self.engineer,
'name': 'Test project',
'tech_used': 'Test tech',
'updated': timezone.now(),
'created': timezone.now(),
'image': None,
'description': 'Test description',
'repo_url': 'https://www.repo.example.com',
'live_url': 'https://www.live.example.com',
'make_public': False
}
self.obj = Project.objects.create(**self.project)
def tearDown(self):
self.obj.image.delete()
def test_upload_image(self):
img_url = r'X:\Path\to\your\image.png'
self.obj.image= SimpleUploadedFile(
name='test_image.jpg',
content=open(img_url, 'rb').read(),
content_type='image/png')
self.obj.save()
self.obj.refresh_from_db()
self.assertNotEqual(self.obj.image, None)
I would like to complement saying that is also possible to implement the test using Python's tempfile.

Pytest-django - testing creation and passing a required User object

Apologies if this has already been answered elsewhere. I cannot find an answer which I can retrofit into my situation.
I'm new to django so I feel the problem is me not getting a fundamental grasp of a presumably basic concept here...
Using DRF and pytest-django, i'm trying to be diligent and write tests along the way before it becomes too time consuming to manually test. I can see it snowballing pretty quickly.
The issue I face is when I try to test the creation of a Catalogue, I can't get it to pass an User instance to the mandatory field 'created_by'. The logic works fine when I test manually, but writing the test itself is causing me headaches.
Many thanks in advance!
The error is:
TypeError: Cannot encode None for key 'created_by' as POST data. Did you mean to pass an empty string or omit the value?
Code provided.
# core/models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
email = models.EmailField(unique=True)
# workshop/models.py
from django.conf import settings
from django.db import models
class Catalogue(models.Model):
STATE_DRAFT = 'DRAFT'
STATE_PUBLISHED_PRIVATE = 'PUBPRIV'
STATE_PUBLISHED_PUBLIC = 'PUBPUB'
STATE_CHOICES = [
(STATE_DRAFT, 'Draft'),
(STATE_PUBLISHED_PRIVATE, 'Published (Private)'),
(STATE_PUBLISHED_PUBLIC, 'Published (Public)')
]
company = models.ForeignKey(Company, on_delete=models.PROTECT)
title = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
state = models.CharField(
max_length=10, choices=STATE_CHOICES, default=STATE_DRAFT)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
created_on = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.title
class CatalogueItem(models.Model):
TYPE_GOODS = 'GOODS'
TYPE_SERVICES = 'SERVICES'
TYPE_GOODS_AND_SERVICES = 'GOODS_AND_SERVICES'
TYPE_CHOICES = [
(TYPE_GOODS, 'Goods'),
(TYPE_SERVICES, 'Services'),
(TYPE_GOODS_AND_SERVICES, 'Goods & Services')
]
catalogue = models.ForeignKey(
Catalogue, on_delete=models.CASCADE, related_name='catalogueitems')
type = models.CharField(
max_length=50, choices=TYPE_CHOICES, default=TYPE_GOODS)
name = models.CharField(max_length=255)
description = models.TextField()
unit_price = models.DecimalField(max_digits=9, decimal_places=2)
can_be_discounted = models.BooleanField(default=True)
def __str__(self) -> str:
return self.name
#property
def item_type(self):
return self.get_type_display()
# workshop/serializers.py
class CatalogueSerializer(serializers.ModelSerializer):
catalogueitems = SimpleCatalogueItemSerializer(
many=True, read_only=True)
created_on = serializers.DateTimeField(read_only=True)
created_by = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
# depth = 1
model = Catalogue
fields = ['id', 'title', 'description',
'state', 'catalogueitems', 'created_by', 'created_on']
def create(self, validated_data):
company_id = self.context['company_id']
user = self.context['user']
return Catalogue.objects.create(company_id=company_id, created_by=user, **validated_data)
# workshop/views.py
class CatalogueViewSet(ModelViewSet):
serializer_class = CatalogueSerializer
def get_permissions(self):
if self.request.method in ['PATCH', 'PUT', 'DELETE', 'POST']:
return [IsAdminUser()]
return [IsAuthenticated()]
def get_queryset(self):
user = self.request.user
if user.is_staff:
return Catalogue.objects.prefetch_related('catalogueitems__catalogue').filter(company_id=self.kwargs['company_pk'])
elif user.is_authenticated:
return Catalogue.objects.filter(company_id=self.kwargs['company_pk'], state='PUBPUB')
def get_serializer_context(self):
company_id = self.kwargs['company_pk']
return {'company_id': company_id, 'user': self.request.user}
# workshop/tests/conftest.py
from core.models import User
from rest_framework.test import APIClient
import pytest
#pytest.fixture
def api_client():
return APIClient()
#pytest.fixture
def authenticate(api_client):
def do_authenticate(is_staff=False):
return api_client.force_authenticate(user=User(is_staff=is_staff))
return do_authenticate
# workshop/tests/test_catalogues.py
from core.models import User
from workshop.models import Catalogue
from rest_framework import status
import pytest
#pytest.fixture
def create_catalogue(api_client):
def do_create_catalogue(catalogue):
return api_client.post('/companies/1/catalogues/', catalogue)
return do_create_catalogue
class TestCreateCatalogue:
def test_if_admin_can_create_catalogue_returns_201(self, authenticate, create_catalogue):
user = authenticate(is_staff=True)
response = create_catalogue(
{'title': 'a', 'description': 'a', 'state': 'DRAFT','created_by':user})
assert response.status_code == status.HTTP_201_CREATED
I think you may have a problem with the user that you are using to do the test,
when you call authenticate it returns a client which is not the same as a user.
then you run the authenticate and log in as a generic user. Try making another fixture that creates a user first, authenticate with that user to return the client and then post that user you created to create_catalogue
from django.conf import settings
#pytest.fixture
def create_user() -> User:
return settings.AUTH_USER_MODEL.objects.create(
username="Test User", password="Test Password", email="testuser#example.com"
)
#pytest.fixture
def authenticate(api_client):
def do_authenticate(create_user):
return api_client.force_authenticate(create_user)
return do_authenticate
class TestCreateCatalogue:
def test_if_admin_can_create_catalogue_returns_201(self, authenticate, create_user create_catalogue):
user = authenticate(create_user)
response = create_catalogue(
{'title': 'a', 'description': 'a', 'state': 'DRAFT','created_by':create_user})
assert response.status_code == status.HTTP_201_CREATED

How to save file in django rest

I have problem with saving files to my server. I need to upload and save files, but id doesn't work for me. I can send file from my UI to my rest api, but i am not able to save the files.
My Models
fs = FileSystemStorage(location='/media/attachments/')
...
class TicketLog(models.Model):
ticket = models.ForeignKey(Ticket, on_delete=models.DO_NOTHING, related_name='ticket_log')
created_date = models.DateTimeField(auto_now=False, auto_now_add=True)
created_by = models.ForeignKey(
User,
on_delete=models.DO_NOTHING,
related_name='ticketlog_creator')
note = models.TextField()
class Meta:
ordering = ['pk']
def __str__(self):
return self.note
class TicketFiles(models.Model):
ticketlog = models.ForeignKey(TicketLog, on_delete=models.CASCADE, related_name='log_file', blank=True, null=True)
attachment = models.FileField(upload_to='attachments', storage=fs)
class UploadWithFile(APIView):
parser_classes = [MultiPartParser, ]
def post(self, request):
content = request.data['file']
data = json.loads(request.data['data'])
queue = Queue.objects.get(pk=data['queue']['id'])
priority = Priority.objects.get(pk=data['priority']['id'])
status = Status.objects.get(pk=data['status']['id'])
created_by = User.objects.get(pk=data['ticket_log'][0]['created_by_id'])
ticket_data = Ticket(
name=data['name'],
priority=priority,
status=status,
queue=queue,
created_date=datetime.datetime.now(),
created_by=created_by
)
ticket_data.save()
log_data = TicketLog(
ticket=ticket_data,
created_date=datetime.datetime.now(),
created_by=created_by,
note=data['ticket_log'][0]['note']
)
log_data.save()
file_data = TicketFiles(
ticketlog=log_data
)
file_data.attachment.save(content.name, content)
file_data.save()
return HttpResponse(content)
It seems that everything works fine and the database is updated correctly, but the files are not saved on server.
An example using rest_framework.parsers ==>> FileUploadParser:
url.py:
urlpatterns = +[
url(r'^docs/$', views.DocumentView.as_view(), name='docs'),
]
models.py
class Document(models.Model):
DOC_CATEGORY = (
('profile_pic', 'Profile_Picture'),
)
class Meta:
ordering = ['uploaded_at']
uploaded_at = models.DateTimeField(auto_now_add=True)
file = models.FileField(blank=False, null=False)
# description
remark = models.CharField(max_length=200, blank=True, null=True)
user = models.ForeignKey(User, blank=True, on_delete=models.DO_NOTHING,)
category = models.CharField(
'Document category',
max_length=64,
choices=DOC_CATEGORY,
default='profile_pic')
def __str__(self):
return self.file.name
serializers.py
class DocumentSerializer(serializers.ModelSerializer):
class Meta():
model = Document
fields = ('file', 'remark', 'uploaded_at', 'user', 'category')
and last views.py:
from rest_framework.parsers import FileUploadParser
class DocumentView(APIView):
http_method_names = ['get', 'post']
model = Document
# fields = ['upload', ]
success_url = reverse_lazy('/')
parser_class = (FileUploadParser,)
# permission_classes = [DocumentViewPerm, ]
# allow any for the example!!!
permission_classes = [AllowAny, ]
def get_object(self, pk):
return serializers.serialize(
'json', list(Document.objects.filter(pk=pk))
)
def get(self, request, pk=None, format=None):
category = request.query_params.get('category', None)
if request.query_params.get('pk'):
return HttpResponse(
self.get_object(pk=request.query_params.get('pk'),
content_type="application/json")
return HttpResponse(self.get_object(request.user.pk),
content_type="application/json")
def post(self, request, *args, **kwargs):
file_serializer = DocumentSerializer(data=request.data)
########################################################
# when uploading keep these headers- no content type !!!
# headers = {'Authorization': '{}'.format(token), 'Accept':
# 'application/json'}
########################################################
if file_serializer.is_valid():
file_serializer.save(user=self.request.user)
# save all fields
# remark # category
remark = request.data.get('remark')
category = request.data.get('category')
return Response(file_serializer.data,
status=status.HTTP_201_CREATED)
else:
return Response(file_serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
#
EXAMPLE upload a file, open python cmd line
import requests, json
# no content type in headers !!!
headers = {'Accept': 'application/json',
'Authorization': "TOKEN"}
with open('pathtofile', 'rb') as f:
r = requests.post('http://MachineIP/docs/', files={'file': f}, data={'remark': 'my remark'}, headers=headers)
You do not have to write your custom code. Using ModelSerializer, you can achieve what you want.
class TicketFilesSerializer(serializer.ModelSerializer):
class Meta:
model = TicketFiles
fields = '__all__'
You can add other fields that you want according to your requirement.
In your API view,
class YourView(generics.GenericApiView):
serializer_class = TicketFilesSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(request.data)
if serializer.is_valid():
serializer.save()
You have to send data in multipart format.

How can I link an edit page to my detail profile page in django?

So I am trying to link the template where I can edit a user_profile like this:
Edit
But is giving me this error:
NoReverseMatch at /user_profile/9/
Reverse for 'user_profile_update' with arguments '()' and keyword arguments '{u'id': ''}' not found. 1 pattern(s) tried: [u'user_profile/(?P\d+)/edit/$']
But I can get access to the template like this without an error: /user_profile/(id)/edit
This is my view:
def user_profile_update(request, id=None):
instance = get_object_or_404(user_profile, id=id)
form = user_profileForm(request.POST or None, request.FILES or None, instance=instance)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect(instance.get_absolute_url())
context = {
"first_name": instance.first_name,
"instance": instance,
"form":form,
}
return render(request, "user_profile/user_profile_form.html", context)
This is my url:
url(r'^create/$', user_profile_create,name='create'),
url(r'^(?P<id>\d+)/$', user_profile_detail, name='detail'),
url(r'^(?P<id>\d+)/edit/$',user_profile_update, name='edit'),
url(r'^(?P<id>\d+)/delete/$', user_profile_delete),
And this is my model:
class user_profile(models.Model):
first_name = models.CharField(null=True,max_length=100)
last_name = models.CharField(null=True,max_length=100)
address_1 = models.CharField(_("Address"), max_length=128)
address_2 = models.CharField(_("Address 1"), max_length=128, blank=True)
city = models.CharField(_("City"), max_length=64, default="pune")
country_name = models.CharField(max_length=60)
pin_code = models.CharField(_("pin_code"), max_length=6, default="411028")
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
def __unicode__(self):
return self.first_name
def __str__(self):
return self.first_name
def get_absolute_url(self):
return reverse("user_profile:detail", kwargs={"id": self.id})
class Meta:
ordering = ["-timestamp", "-updated"]
I would be really glad if someone could help me!
You need separate views and URL-conf for editing and detail view. You only have ^user_profile/(?P\d+)/edit/$' in your URL-conf, so you can only access the view from user_profile/123/edit/. So you need to add another URL '^user_profile/(?P\d+)/$ to access from user_profile/123/.
The same with views, you need two separate ones for the simplest solution.

Test client doesn't throw expected 404 error

I'm facing a weird error in which a Django test I wrote to test a view's response isn't giving me the expected behavior.
Consider the following model for a simple blogging application:
class Post(models.Model):
"""Represents a Post in the blogging system."""
# is the post published or in draft stage?
STATUS_CHOICES = (
('draft', 'Draft'),
('published', 'Published'),
('deleted', 'Deleted'),
)
# the default mode for posts
DRAFT = STATUS_CHOICES[0][0]
title = models.CharField(max_length=250, null=True)
slug = models.SlugField(max_length=200, blank=True)
text = models.TextField(null=True)
last_modified = models.DateTimeField(default=timezone.now)
date_published = models.DateTimeField(default=timezone.now, null=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=DRAFT)
category = models.ForeignKey('Category', null=True)
author = models.ForeignKey('Author', null=True)
class Meta:
ordering = ('-date_published',)
#property
def truncated_text(self):
if len(self.text) > 200:
return '{} . . .'.format(self.text[0:300])
else:
return self.text
def publish(self):
self.date_published = timezone.now()
self.status = 'published'
self.save()
def save(self, *args, **kwargs):
# Set slug only on new posts
if not self.id:
self.slug = slugify(self.title)
self.last_modified = timezone.now()
super().save(*args, **kwargs)
def __str__(self):
return self.title
Here's the view (you can see that I'm insisting on getting published posts only):
def post_view(request, slug):
post = get_object_or_404(Post, slug=slug, status='published')
return render(request, 'blog/single_post.html', {'post': post})
Here's the URL entry:
url(r'^blog/(?P<slug>[-\w]+)/$', blog_view.post_view, name='single_post'),
And finally, here's the test:
class TestBlogViews(TestCase):
"""Class to test the views of our blog app"""
def setUp(self):
"""Make sure we have an environment in which to render the templates"""
# An author
self.author = Author()
self.author.name = 'Ankush'
self.author.short_name = 'ank'
self.author.save()
# A category
self.cat = Category()
self.cat.name = 'Programming'
self.cat.save()
# A post
self.post = Post()
self.post.title = 'Dummy title'
self.post.text = 'Dummy text that does nuffin'
self.post.author = self.author
self.post.category = self.cat
setup_test_environment()
self.client = Client()
def test_unpublished_post_raises_404(self):
self.post.save()
response = self.client.get(reverse('single_post', args=('dummy-title',)))
self.assertEqual(response.status_code, 404)
So basically, my test fails because I'm getting 200 != 404. I also ran the Post api from shell, and got a DoesNotExist when looking for status = 'published' on a post that only had the save() method called. I also printed the response.content in my test, and it contains the custom 404 template content. Why isn't it throwing 404 when the object clearly doesn't exist? Something wrong with get_object_or_404()?
P.S. Please let me know if you need more snippets of code.
All right, I was able to figure it out. Turns out my custom 404 view was screwed up:
It was:
def not_found(request):
return render(request, 'ankblog/not_found.html')
So naturally the status code was 200. It got fixed by changing it to return render(request, 'ankblog/not_found.html', status=404).
I do find it funny, though. I would've thought that the custom 404 would have raised a 404 error automatically.