I'm learning how to use Mock for testing in django (2.0.6 <= it can make the difference) but I'm stuck following a tutorial.
Here my test_views.py:
User = get_user_model() #I use this in other tests, I hope it don't interfere
#patch('lists.views.NewListForm')
class NewListViewUnitTest(unittest.TestCase):
def setUp(self):
self.request = HttpRequest()
self.request.POST['text'] = 'new list item'
self.request.user = Mock()
def test_passes_POST_data_to_NewListForm(self, mockNewListForm):
print('self.request', self.request.user)
new_list2(self.request)
mockNewListForm.assert_called_once_with(data=self.request.POST)
[...]
And here my error on testing:
self.request <Mock name='mock()' id='80288792'> #print result
E
======================================================================
ERROR: test_passes_POST_data_to_NewListForm (lists.tests.test_views.NewListViewU
nitTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "d:\python\Lib\unittest\mock.py", line 1179, in patched
return func(*args, **keywargs)
File "D:\progetti\superlists\lists\tests\test_views.py", line 149, in test_pas
ses_POST_data_to_NewListForm
new_list2(self.request)
File "D:\progetti\superlists\lists\views.py", line 42, in new_list2
list_.owner = request.user
File "C:\Users\fabio\.virtualenvs\superlist\lib\site-packages\django\db\models
\fields\related_descriptors.py", line 197, in __set__
self.field.remote_field.model._meta.object_name,
ValueError: Cannot assign "<Mock name='mock()' id='80288792'>": "List.owner" must be a "User" instance.
So, it look like it don't recognize the self.request.user = Mock() like a User instance.
Note on the test_view:
1) the print command
2) the User = get_user_model() variable. get_user_model() comes from django.contrib.auth. I think it don't interfere.
I add some more code for completeness, ask for more if you need.
My views.py:
def new_list2(request):
form = NewListForm(data=request.POST)
if form.is_valid():
list_ = List()
if request.user.is_authenticated:
list_.owner = request.user
list_.save()
form.save(owner=request.user)
return redirect(list_)
return render(request, 'home.html', {'form': form})
My forms.py:
class ItemForm(forms.models.ModelForm):
class Meta:
model = Item
fields = ('text',)
widgets = {
'text': forms.fields.TextInput(attrs={
'placeholder': 'Enter a to-do item',
'class': 'form-control input-lg',
}),
}
error_messages = {
'text': {'required': EMPTY_ITEM_ERROR}
}
def save(self, for_list):
self.instance.list = for_list
return super().save()
class NewListForm(ItemForm):
def save(self, owner):
if owner.is_authenticated:
return List.create_new(first_item_text=self.cleaned_data['text'],
owner=owner)
else:
return List.create_new(first_item_text=self.cleaned_data['text'])
My models.py:
class List(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
on_delete=models.PROTECT)
def get_absolute_url(self):
return reverse('view_list', args=[self.id])
#staticmethod
def create_new(first_item_text, owner=None):
list_ = List.objects.create(owner=owner)
Item.objects.create(text=first_item_text, list=list_)
return list_
#property
def name(self):
return self.item_set.first().text
#def __str__(self):
# return self.item_set.first().text
class Item(models.Model):
text = models.TextField(default='')
list = models.ForeignKey(List, default=None, on_delete=models.PROTECT)
class Meta:
ordering = ('id',)
unique_together = ('list', 'text')
def __str__(self):
return self.text
Please, any help will be appreciated
Related
I have a CreateView with which I create new blog posts, I want to test it in order to check if everything is ok but something is wrong with my test and I can't understand what exactly. it gives me 2 errors, for the first method I get this error:
Traceback (most recent call last):
File "C:\Users\Bularu Lilian\Desktop\EcoMon\blog\tests\test_views.py", line 73, in test_post_create_view_GET
self.assertEquals(response.status_code, 200)
AssertionError: 302 != 200
and for the second one is this error:
File "C:\Users\Bularu Lilian\Desktop\EcoMon\blog\tests\test_views.py", line 78, in test_post_create_view_POST_success
post = Post.objects.get(title=self.post['title'])
File "C:\Users\Bularu Lilian\Desktop\Environments\ecomon\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "C:\Users\Bularu Lilian\Desktop\Environments\ecomon\lib\site-packages\django\db\models\query.py", line 429, in get
raise self.model.DoesNotExist(
blog.models.Post.DoesNotExist: Post matching query does not exist.
This is my Test class:
class TestPostCreateViews(BaseTest):
def test_post_create_view_GET(self):
response = self.client.get(self.add_post_url)
self.assertEquals(response.status_code, 200)
self.assertTemplateUsed(response, 'blog/add_post.html')
def test_post_create_view_POST_success(self):
response = self.client.post(self.add_post_url, self.post, author=self.user, format='text/html')
post = Post.objects.get(title=self.post['title'])
self.assertEquals(response.status_code, 302)
self.assertEquals(post.title, 'test post')
my CreateView:
class PostCreateView(LoginRequiredMixin, IsSuperuserOrStaffMixin, CreateView):
template_name = 'blog/add_post.html'
form_class = PostCreateForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
my url path:
path('new/post/', PostCreateView.as_view(), name='add-post'),
my form:
class PostCreateForm(forms.ModelForm):
title = forms.CharField(widget=forms.TextInput(), max_length=200)
content = forms.CharField(widget=forms.Textarea(attrs={'rows': 25, 'cols': 50}))
class Meta:
model = Post
exclude = ['author', 'slug', 'published_date', 'updated_date']
and my model:
class Post(models.Model):
class PostCategory(models.TextChoices):
FAMILY = 'FAMILY', _('Family')
BUSINESS = 'BUSINESS', _('Business')
MWRKETING = 'MARKETING', _('Marketing')
SPENDINGS = 'SPENDINGS', _('Spendings')
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(_('Title'), max_length=200, unique=True)
content = models.TextField(_('Content'))
category = models.CharField(_('Category'), max_length=9, choices=PostCategory.choices, default=PostCategory.BUSINESS)
slug = models.SlugField(_('Slug'), max_length=200, blank=True, null=False, unique=True)
tags = TaggableManager(_('Tags'))
published_date = models.DateTimeField(_('Published Date/Time'), auto_now_add=True)
updated_date = models.DateTimeField(_('Updated Date/Time'), auto_now=True)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super().save(*args, **kwargs)
#property
def comments_count(self):
return self.comments.count()
def get_absolute_url(self):
return reverse('blog')
for any help I would be greatefull
Try to write it like this:
def test_post_create_view_POST_success(self):
data = {
'author': self.user,
'title': 'test_title',
'content': 'test_content',
'category': 'test_category',
'slug': 'test_slug',
'tags': None, # or create some tags and populate
}
response = self.client.post(self.add_post_url, data=data, follow=True) # make sure your url is correct too btw that could also be the issue
self.assertEquals(response.status_code, 200)
self.assertEquals(Post.objects.filter(title='test_title').count(), 1)
Ive been trying to create an API that would return all objects from Like model however, I received an error (Expected view likeList to be called with a URL keyword argument named "id". Fix your URL conf, or set the .lookup_field attribute on the view correctly.).
Here is my model
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
objects = models.Manager()
image = models.ImageField(upload_to='post_pics')
def __str__(self):
return self.title
#property
def useremail(self):
return self.author.email
#property
def owner(self):
return self.author
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk':self.pk})
def get_api_url(self, request=None):
return api_reverse('post-detail', kwargs={'pk': self.pk}, request=request)
def get_like_count(self):
return self.like_set.count()
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
Serializer
class likeserializers(serializers.ModelSerializer):
username = serializers.SerializerMethodField(read_only=True)
post_title = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Like
fields = ('id','created',
'user','username',
'post','post_title')
def get_username(self, obj):
return obj.user.username
def get_post_title(self, obj):
return obj.post.title
Views
class likeList(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'id'
serializer_class = likeserializers
def get_queryset(self):
return Like.objects.all()
URLS
urlpatterns = [
path('users/', API_views.userList.as_view(), name = 'users'),
path('users/id=<int:id>/', API_views.userListbyID.as_view(), name = 'usersid'),
path('posts/', API_views.postList.as_view(), name = 'post'),
path('posts/id=<int:id>', API_views.postListbyID.as_view(), name = 'postid'),
path('likes/', API_views.likeList.as_view(), name = 'likes'),
path('likes/id=<int:id>', API_views.likeListbyID.as_view(), name = 'likesid'),
path('follows/', API_views.followList.as_view(), name = 'likes'),
path('follows/id=<int:id>', API_views.followListbyID.as_view(), name = 'likesid'),
]
As per the error I should either fix my URL conf, or set the .lookup_field attribute on the view correctly. It works as intended if I change my URL conf however, if I use only lookup_field it fix the issue. I have completely the same view for posts and it works.
Post serializer:
class postserializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
like_count = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Post
fields = ('url','id',
'title','content',
'date_posted','author',
'useremail','like_count')
def get_url(self,obj):
request = self.context.get("request")
return obj.get_api_url(request=request)
def get_like_count(self,obj):
return obj.get_like_count()
def validate_title(self,value):
qs = Post.objects.filter(title__iexact = value)
#exclude the same instance
if self.instance:
qs = qs.exclude(pk=self.instance.pk)
#if title already exists raise error
if qs.exists():
raise serializers.ValidationError(f"Post with title '{value}' already exists")
return value
and post view:
class postList(mixins.CreateModelMixin, generics.ListAPIView):
lookup_field = 'id'
serializer_class = postserializer
permission_classes = [IsOwnerOrReadOnly]
# permissions can be set up here as well + in settings.py
# permission_classes =
def get_queryset(self):
qs = Post.objects.all()
query = self.request.GET.get("q")
if query is not None:
qs = qs.filter(
Q(title__icontains = query)|
Q(content__icontains = query)
).distinct()
return qs
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
What am I missing? What is the difference between my post and like views/serializers which are causing this issue?
In my django app I have a Myuser(User) class. It inherits the User class.
When a new user is created the Myuser table is poplulated.
myusers.py
class Myuser(User):
address = models.CharField(max_length=40)
pobox = models.CharField(max_length=40)
models.py
class Someclass(models.Model):
objectid = models.IntegerField()
objecttype = models.CharField(max_length=200)
created = models.DateTimeField(default=timezone.now)
modified = models.DateTimeField(auto_now=True)
class Someotherclass(Someclass):
status = models.IntegerField(default=0,)
name = models.CharField(max_length=200)
created = models.DateTimeField(default=timezone.now)
modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User)
forms.py
class SomeotherclassForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
self.user = kwargs.pop('user')
self.app = kwargs.pop('app')
self.table = kwargs.pop('table')
self.mytype = kwargs.pop('mytype')
initial = kwargs.get('initial', {})
super(SomeotherclassForm, self).__init__(*args, **kwargs)
create.py
class DataCreate(CreateView):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
#some code here not relevant at all
def get_form_kwargs(self):
kwargs = super(DataCreate, self).get_form_kwargs()
objectid = self.request.GET.get('objectid',None)
objecttype = self.request.GET.get('objecttype',None)
kwargs.update({'mytype': objecttype})
kwargs.update({'request': self.request})
kwargs.update({'user': self.request.user})
kwargs.update({'app': self.app})
kwargs.update({'table': self.kwargs['table'].lower()})
return kwargs
def form_valid(self, form):
obj = form.save(commit=False)
group = ''
if not self.request.user.is_superuser:
group = MyUser.objects.get(user_ptr_id=self.request.user.pk)
else:
groups = self.request.user.groups.all()
if self.kwargs['table'] == 'Myprotocol':
obj = form.save(commit=False)
table = eval(self.request.GET.get('objecttype',None).title()).objects.get(pk=int(self.request.GET.get('objectid',None)))
obj.objectid = table.pk
obj.objecttype = table.__class__.__name__.lower()
obj.user_id = self.request.user.pk
obj.save()
else:
obj = form.save()
if self.request.POST.get('is_popup'):
check = int(self.kwargs['is_list'])
if self.kwargs['table'] == 'Someclass':
popup = 1
a = checkPopup2(obj,check,popup,obj.pk)
else:
a = checkPopup(obj,check)
return a
else:
return super(DataCreate, self).form_valid(form)
When I have logged in as a regular user ,everything works fine.
When I log in as a superuser, I get form error that objecttype,objectid and user are not filled.
In my attempts to troubleshoot it , I realized that when I am logged in as a superuser ,it dowsn't reach the form_valid() function.
I can't figure out why that is happening.
Any suggestions or advice on how to troubleshoot it?
I have a view which displays a form and saves the same form. There is no problem showing the view but when saving I get this following error:
int() argument must be a string, a bytes-like object or a number, not 'QueryDict'
Here is my code:
MODEL:
class Category(models.Model):
name = models.CharField(max_length=100, db_index=True, unique=True)
slug = models.SlugField(max_length=100, db_index=True, unique=True)
user = models.ForeignKey(User, related_name='categories')
class Meta:
ordering = ('name',)
verbose_name = 'category'
verbose_name_plural = 'categories'
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('wallet:category_details', args=[self.id])
VIEW:
#login_required
def category_create(request):
user = request.user
if request.method == 'POST':
category_create_form = CategoryCreateForm(request.POST)
if category_create_form.is_valid():
new_category = category_create_form.save(commit=False)
new_category.user = request.user
new_category.save()
return render(request, 'category/done.html')
else:
category_create_form = CategoryCreateForm()
return render(request, 'category/create.html', {'category_form': category_create_form})
FORMS:
class TransactionCreateForm(forms.ModelForm):
field_order = ['name', 'type', 'wallet', 'category', 'date', 'amount', 'notes', ]
class Meta:
model = Transaction
fields = ('name', 'type', 'wallet', 'category', 'date', 'amount', 'notes',)
def __init__(self, user, *args, **kwargs):
super(TransactionCreateForm, self).__init__(*args, **kwargs)
self.fields['wallet'] = forms.ChoiceField(
choices=[(wallet, str(wallet)) for wallet in Wallet.objects.filter(user=user)])
How should I solve it?
Your form's __init__ method specifies a user argument, which you're not providing when instantiating it. Create your form with:
category_create_form = CategoryCreateForm(user)
or, in the POST branch,
category_create_form = CategoryCreateForm(user, request.POST)
Well, assuming the actual class looks like the differently named form class you posted. Basically, look at what parameters your form requires.
I have been Googling around for this question for quite long. And sadly no luck. Indeed I found questions like SO question and SO question. I would say I found myself having a very similar question to the first one. However, I do not understand the discussions under that question.
So first of all, to make it clear, I did not even get formset.is_valid() to return True--that is the problem. There are indeed uniqueness check but my edit should be able to pass that check in the database level.
And here comes my code:
### models.py
class DataAccess(models.Model):
user = models.ForeignKey(User, models.CASCADE)
project_data = models.ForeignKey(ProjectData, models.CASCADE)
role = models.CharField(
max_length=2, choices=ROLES, default='N'
)
created_at = models.DateTimeField(auto_now=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = (('user', 'project_data', 'role'),)
# query from project data should happen more
index_together = [['project_data', 'user', 'role']]
def __str__(self):
return '{}&{}&{}'.format(self.user, self.project_data, self.role)
def save(self, *args, **kwargs):
curr = datetime.now()
if not self.created_at:
self.created_at = curr
self.updated_at = curr
return super(DataAccess, self).save(*args, **kwargs)
class DataSyncPath(models.Model):
server = models.ForeignKey(Server, models.CASCADE)
project_data = models.ForeignKey(ProjectData, models.CASCADE)
path = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = (('server', 'project_data',),)
index_together = [['project_data', 'server']]
def __str__(self):
return '{}&{}'.format(self.server, self.project_data)
def save(self, *args, **kwargs):
curr = datetime.now()
if not self.created_at:
self.created_at = curr
self.updated_at = curr
return super(DataSyncPath, self).save(*args, **kwargs)
### forms.py
class DataAccessForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DataAccessForm, self).__init__(*args, **kwargs)
helper = FormHelper()
helper.template = 'bootstrap/table_inline_formset.html'
self.helper = helper
self.helper.form_tag = False
class Meta:
model = DataAccess
exclude = ['created_at', 'updated_at']
def get_data_access_formset(initial=[], extra=3):
return inlineformset_factory(
ProjectData, DataAccess,
form=DataAccessForm, extra=extra + len(initial)
)
class DataSyncPathForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DataSyncPathForm, self).__init__(*args, **kwargs)
helper = FormHelper()
helper.template = 'bootstrap/table_inline_formset.html'
self.helper = helper
self.helper.form_tag = False
class Meta:
model = DataSyncPath
exclude = ['created_at', 'updated_at']
def clean(self):
super(DataSyncPathForm, self).clean()
if (self.cleaned_data['id'] is None and 'path' in self.errors and
self.errors['path'][0] == 'This field is required.'):
# a special hack to silent path error
self.errors.pop('path')
def get_data_sync_path_formset(initial=[], extra=0):
return inlineformset_factory(
ProjectData, DataSyncPath, can_delete=False,
form=DataSyncPathForm, extra=extra + len(initial)
)
### views.py, import and everything is taken care of already
class ProjectDataUpdateView(LoginRequiredMixin, UpdateView):
login_url = reverse_lazy('login')
redirect_field_name = 'redirect_to'
template_name = 'project_data/project_data_form.html'
form_class = ProjectDataForm
context_object_name = 'project_data'
pk_url_kwarg = 'project_data_id'
success_url = reverse_lazy('data_projects:project_data_list')
def get_queryset(self):
return dbmanager.get_project_data_by_user(
self.request.user
)
def get_initial(self):
initial = super(ProjectDataUpdateView, self).get_initial()
try:
initial['phabricator_links'] = ','.join(json.loads(
self.object.phabricator_links))
except Exception:
initial['phabricator_links'] = ''
return initial
def get_data_access_initial(self):
data_access_queryset = self.object.dataaccess_set.all()
data_access_initial = [
model_to_dict(obj) for obj in data_access_queryset
]
return data_access_initial
def get_data_sync_path_initial(self):
datasyncpath_set = self.object.datasyncpath_set.all()
sync_path_initial = [
model_to_dict(obj) for obj in datasyncpath_set
]
servers = self.request.user.server_set.all()
missing_servers = set(
[server.id for server in servers]) - set(
[obj['server'] for obj in sync_path_initial]
)
for server_id in missing_servers:
sync_path_initial.append({'server': server_id})
return sync_path_initial
def process_context_data(self, data):
data_access_initial = self.get_data_access_initial()
sync_path_initial = self.get_data_sync_path_initial()
if self.request.POST:
if (self.request.user.is_superuser or
self.request.user.groups.filter(name='Manager').exists()):
data['data_access_formset'] = get_data_access_formset()(
self.request.POST, self.request.FILES,
instance=self.object, initial=data_access_initial,
queryset=self.object.dataaccess_set.all()
)
data['data_sync_path_formset'] = get_data_sync_path_formset()(
self.request.POST, self.request.FILES,
instance=self.object, initial=sync_path_initial,
queryset=self.object.datasyncpath_set.all()
)
else:
data['data_access_formset'] = get_data_access_formset(
initial=data_access_initial
)(initial=data_access_initial)
data['data_sync_path_formset'] = get_data_sync_path_formset(
initial=sync_path_initial
)(initial=sync_path_initial)
return data
def get_context_data(self, **kwargs):
data = super(ProjectDataUpdateView, self).get_context_data(**kwargs)
data_access_formset = kwargs.pop('data_access_formset', None)
data_sync_path_formset = kwargs.pop('data_sync_path_formset', None)
if data_access_formset and data_sync_path_formset:
data['data_access_formset'] = data_access_formset
data['data_sync_path_formset'] = data_sync_path_formset
else:
data = self.process_context_data(data)
data['is_data_sync_path_set'] = True
return data
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not (self.request.user.is_superuser or
self.request.user.groups.filter(name='Manager').exists()):
self.request.POST['name'] = self.object.name
self.request.POST['dmgr'] = self.object.dmgr
form_class = self.get_form_class()
form = self.get_form(form_class)
context_data = self.get_context_data()
data_access_formset = context_data.get('data_access_formset')
sync_path_formset = context_data.get('data_sync_path_formset')
if (form.is_valid() and
data_access_formset.is_valid() and
sync_path_formset.is_valid()):
return self.form_valid(
form, data_access_formset, sync_path_formset)
else:
return self.form_invalid(
form, data_access_formset, sync_path_formset)
def form_valid(
self, form, data_access_formset, sync_path_formset, **kwargs):
for deleted_form in data_access_formset.deleted_forms:
try:
deleted_form.cleaned_data['id'].delete()
except Exception:
pass
self.object = form.save()
data_access_formset.save()
sync_path_formset.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(
self, form, data_access_formset, sync_path_formset, **kwargs):
return self.render_to_response(
self.get_context_data(
form=form, data_access_formset=data_access_formset,
sync_path_formset=sync_path_formset
)
)
I have also put breakpoint in post method of the UpdateView class:
ipdb> sync_path_formset.is_valid()
False
ipdb> sync_path_formset.forms[0]
<DataSyncPathForm bound=True, valid=False, fields=(server;project_data;path;id;DELETE)>
ipdb> sync_path_formset.forms[0].cleaned_data
{'DELETE': False, 'project_data': <ProjectData: bbb>, 'path': 'that', 'server': <Server: jgu-pc>, 'id': <DataSyncPath: jgu-pc&bbb>}
ipdb> sync_path_formset.forms[0].instance
<DataSyncPath: jgu-pc&bbb>
ipdb> sync_path_formset.forms[0].save(commit=False)
*** ValueError: The DataSyncPath could not be created because the data didn't validate.
ipdb> sync_path_formset.forms[0].has_changed()
True
So clearly the instance exists. But it just tries to create another instance anyway.