Unittest a Django model Clean method - python

I'm working on a simple Django social media project and I'm trying to create a unittest to verify that a User cannot like a post that they created. I want to do one of the following
Write a unittest to test a clean method of a model
or
Write a CheckConstraint to prevent a User and Post creator from being the same Can't create a Check on a related model - https://forum.djangoproject.com/t/checkconstraint-involving-related-model/5351
My Model
In my model I have a UniqueConstraint to prevent a user from liking a post more than once. I initially tried to create a CheckConstraint to prevent a user from like their own post, but couldn't figure out how to do that. An alternative I came upon was to create a clean method that achieved the same goal.
# From models.py
class Post(models.Model):
creator = models.ForeignKey(User, on_delete=models.CASCADE, related_name="creator")
content = models.CharField(max_length=160)
pub_date = models.DateTimeField('date posted', default=timezone.now)
class Like(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="likes")
user = models.ForeignKey(User, on_delete=models.CASCADE)
like_unlike = models.BooleanField(choices=BOOL_CHOICES)
def clean(self):
"""Prevent User from liking/unliking their own post"""
if self.user == self.post.creator:
raise ValidationError(_("User cannot like/unlike their own post"))
class Meta:
constraints = [
# Prevent a user from liking/unliking the same post twice
models.UniqueConstraint(fields=['user', 'post'], name="unique like"),
]
My Unittests
When creating my unittests I have been able to test the unique constraint but unable to successfully test the clean method. My expectation was that when I attempt to create a Like entry with a User the same as the Post creator that I would raise an error. I used Like.objects.create
and when that didn't work, tried assigning Like() to a variable and assigned it's values before calling save on it. However both ways seem to bypasses the clean method when it saves to the databases.
# From test_models.py
class LikeTestCase(TestCase):
def setUp(self) -> None:
User.objects.create(username="john", email="john#email.com")
user = User.objects.create(username="mary", email="mary#email.com")
Post.objects.create(creator=user, content=f"post-1", pub_date=timezone.now())
def test_cannot_like_post_multiple_times(self):
"""Verify user cannot like a post more than once"""
post = Post.objects.get(id=1)
user = User.objects.get(name="john")
Like.objects.create(post=post, user=user, like_unlike=True)
with self.assertRaises(IntegrityError):
Like.objects.create(post=post, user=user, like_unlike=True)
def test_cannot_like_own_post_1(self):
"""Verify user cannot like their own post"""
post = Post.objects.get(id=1)
like = Like()
like = Like.objects(post=post, user=post.creator, like_unlike=True)
like.save()
def test_cannot_like_own_post_2(self):
"""Verify user cannot like their own post"""
post = Post.objects.get(id=1)
Like.objects.create(post=post, user=post.creator, like_unlike=True)

From this previous answer the key was to call full_clean() on the model object. My test then became
def test_cannot_like_own_post(self):
"""Verify user cannot like their own post"""
post = Post.objects.get(id=1)
with self.assertRaises(ValidationError):
like = Like(post=post, user=post.creator, like_unlike=True)
like.full_clean()

Related

Tried databse queryset on crontab but it doesn't work

Crontab works fine on simple task.
e.g
def test():
test.objects.create.(name='Dino')
#It also works on this
def test_task()
if Users_Machine.objects.filter().exists():
test.objects.create(name='Dino')
but when i tried to query my db with foreignkey it does nothing.
Have tried this two methods:
#request method
def test_task(request):
if Users_Machine.objects.filter(user=request.user).exists():
test.objects.create(name='Dino', user=request.user)
#direct methods
def test_task()
if Users_Machine.objects.filter().exists():
name=Users_Machine.objects.get()
test.objects.create(name='Dino', user=name.username)
The Users_Machine.object returns True
Here is my models
#users_machine model
class Users_Machine(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
Name = models.CharField(max_length=20)
#test model
class test(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
Please how can i make this work?
It would be better if I get to look at User model but for now I am going to assume in the request, you are passing Name field which is present in the UserMachine model and you can create new object in test model as follows:
def test_task(request):
Current_user = User_Machine.objects.get(Name = request.name)
if Current_user:
test.objects.create(name='Dino', user=Current_user)
Can you please add User model as well, that will allow me fine tune the answer (or correct it if this one is wrong)?

Django: Block access to not authorized users in CreateView

I have a problem for block access to not authorized user in pages dedicated to add new objects. List of that users is stored in many-to-many field in project object and in foreign key field.
Below is models.py
class Project(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="projects_as_owner", null=True)
project_managers = models.ManyToManyField(User, related_name="projects_as_pm", blank=True)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
date_of_insert = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Milestone(models.Model):
project_fk = models.ForeignKey(Project, related_name="milestones", on_delete=models.CASCADE)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
date_of_insert = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
And views.py with class I have problem
class NewMilestone(LoginRequiredMixin, generic.CreateView):
model = Milestone
fields = ['name', 'description']
lookup_url_kwarg = 'p_id'
template_name = 'main/new_milestone.html'
# ... two functions, that work good, not important here ...
def get_queryset(self):
qs = super(NewMilestone, self).get_queryset()
project = Project.objects.get(id=self.kwargs['p_id'])
if(qs.filter(project_fk__owner=self.request.user).exists() or User.objects.filter(id=self.request.user.id).filter(projects_as_pm__id=project.id).exists()):
return qs
else:
return Http404("You are not authenticated to this action")
Objective here is here to allow authenticated users (owner and project manager/s) to enter this view and for anybody else show info about declined access.
Problem is that, that method, get_queryset, doesn't block unauthorised users in CreateViev class.
I tried some configurations for that issue, every single one I used had this flaw.
My question here is how to make it work the way I expect from it?
PS. English is not my native language and it was a while since I wrote something, so please be understanding.
You are using the LoginRequiredMixin which is a good thing. But then you didn't set any of the parameters available.
LoginRequiredMixin inherits from AccessMixin and you can use all it's parameters with which it shouldn't be too complicated to cover your case.
Here's a possible implementation:
class NewMilestone(LoginRequiredMixin, generic.CreateView):
...
# your class attributes
...
raise_exception = True
# Returns a permission denied message. Default: empty string
def get_permission_denied_message(self):
return "Access is restricted to authenticated users"
If you have raise_exception set to True then the get_permission_denied_message method will be called. Otherwise the user will be redirected to the login_url which you also would have to declare as a class attribute.

User Follower model on Django. Cannot use add() on a ManyToManyField which specifies an intermediary model. Use accounts.Contact's Manager instead

I am new to Django, Please forgive any silly mistakes in code or logic,
Intro: I am trying to create a user follower model in Django. Where users can follow and unfollow other users on the sites
Error: I have made the models for my follow/unfollow I have also made the views I am getting this error
AttributeError at /accounts/admin/follow/
Cannot use add() on a ManyToManyField which specifies an intermediary model. Use accounts.Contact's Manager instead.
The obj.followers.add(user) is highlighted in the traceback as the origin of the error
Below are my models.py
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
city = models.CharField(max_length=100)
country = models.CharField(max_length=100)
def get_absolute_url(self):
return reverse('accounts:profile', kwargs={'username': self.user.username})
class Contact(models.Model):
user_from = models.ForeignKey(User, related_name='suppporter')
user_to = models.ForeignKey(User, related_name='leader')
def __str__(self):
return '{} follows {}'.format(self.user_from, self.user_to)
User.add_to_class('following',
models.ManyToManyField('self', through=Contact, related_name='followers', symmetrical=False))
I think the models.py may be good. The fault I believe is in my views.
Below is my view.py
class FollowToggle(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
username = self.kwargs.get('username')
print(username + " This is the user who will be followed") # This prints correct
profile = get_object_or_404(Profile, user__username=username)
print(profile) # This prints correct
obj = get_object_or_404(User, username=username)
print(obj) # This prints correct
url_ = profile.get_absolute_url()
print(url_) # This prints correct
user = self.request.user
print(user) # This prints correct
if user.is_authenticated():
if user in obj.followers.all(): # I know this is the source of the error.
obj.followers.remove(user)
else:
obj.followers.add(user)
return url_
Below are the Urls.py just in case
url(r'^(?P<username>[-\w]+)/follow/$', views.FollowToggle.as_view(), name='follow'),
You cannot use add and remove method for manytomany relation defined through third model. From the docs:
Unlike normal many-to-many fields, you can’t use add(), create(), or set() to create relationships
Instead you should use Contact manager:
if user.is_authenticated():
if user in obj.followers.all(): # I know this is the source of the error.
Contact.objects.filter(user_to=obj, user_from=user).delete()
else:
Contact.objects.create(user_to=obj, user_from=user)
In Django 2.2 you can use add, remove and set methods (Docs)
You can also use add(), create(), or set() to create relationships, as long as your specify through_defaults for any required fields

ValueError: Lookup failed for model referenced by field

I have made Custom User model in my Django project. Here it is:
class CustomUser(User):
avatar = models.ImageField(upload_to='avatars')
about_myself = models.TextField(max_length=300)
USERNAME_FIELD = 'username'
def __str__(self):
return self.username
def is_author(self):
return 'blog.change_post' and 'blog.add_post' in self.get_all_permissions()
And after it, I changed all Foreign Keys of user to new CustomUser model. It works OK. But I make one new migration and django cause error, when I want to migrate it:
ValueError: Lookup failed for model referenced by field blog.Comment.author: main.CustomUser
My blog.Comment model:
class Comment(models.Model):
content = models.TextField()
author = models.ForeignKey(CustomUser)
date_create = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey(Post)
What should I do?
Thanks!
Judging from the code you posted, you might be might be better served by extending the user model rather than replacing it. This pattern is usually called a profile model and works via a one-to-one relationship with User.
Profiles provides application specific fields and behaviors, while allowing User to go about it's usual business unchanged. It doesn't require you to muck around with rewriting auth or even necessarily change your foreign keys.
Here's an example of your code written as a profile:
class Profile(models.Model):
# Link to user :
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
avatar = models.ImageField(upload_to='avatars')
about_myself = models.TextField(max_length=300)
def __str__(self):
return self.user.username
def is_author(self):
return 'blog.change_post' and 'blog.add_post' in self.user.get_all_permissions()
Comment model:
class Comment(models.Model):
content = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL)
date_create = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey(Post)
# How to access the profile:
def check_author(self):
self.author.profile.is_author()
You'll also want to add a signal to create a new profile when a user is registered:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_for_new_user(sender, created, instance, **kwargs):
if created:
profile = Profile(user=instance)
profile.save()
Django docs on extending users.
If a profile approach doesn't work for you, try inheriting from AbstractUser or AbstractBaseUser instead of User. The abstract models provide the same basic functionality as User and are the preferred technique for recent Django versions.
There are a handful of additional steps however, check out the docs on creating custom users for a run down.

Save user specific data in Django

I am hacking Django, as I am new to it, to create a website to which user can login and can answer some questions. For logged in user I intend to store their username, question id and response to the question. However, after trying for multiple hours I have been completely unsuccessful. Below I have given snippets of Models.py and Views.py
Models.py - I am copying only UserProfile class and Userresponse class which are needed to create the User Profile and User Response table
# Model class for creating user
class UserProfile(models.Model):
user = models.OneToOneField(User)
def __str__(self):
return self.user.username
# Model class for getting user response
class UserResponse1(models.Model):
user = models.ForeignKey(UserProfile, default=0)
questoinId = models.ForeignKey(Question)
option = models.IntegerField(default=0)
```Views.py``
def response(request, question_id):
q = UserResponse1()
if request.user.is_authenticated():
q.user = request.user.username
q.questionId_id = question_id
q.option +=request.POST['choice']
q.save()
# Redisplay the question voting form.
return HttpResponseRedirect(reverse('polls:overallResults'))
However, on running above I get following error - Cannot assign "u'abc123'": "UserResponse1.user" must be a "UserProfile" instance.
abc123 is the login name of the user. I am not able to figure out the reason for this error. Any help on fixing this error so that I can write the data to UserResponse1 table, will be very helpful.
I am using Django 1.8 on Python 2.7
q.user is a foreign key to the UserProfile table, so you have to assign a user profile instance.
Since you have access to the user with request.user you can access the user profile using the one to one field.
user_profile = request.user.userprofile
q.user = user_profile

Categories