ValueError: Lookup failed for model referenced by field - python

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.

Related

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.

Unittest a Django model Clean method

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()

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

Multiple User Types In Django

I am new to Django and trying to create an App with two User Types (Freelancers and Customers). I understand how to create a User profile Class and it works well for me:
class UserProfile(models.Model):
user = models.OneToOneField(User)
description = models.CharField(max_length=100, default='')
country = models.CharField(max_length=100, default='')
website = models.URLField(default='')
phone = models.IntegerField(default=0)
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender=User)
This works well for me on a one user type user. But now I am building an app with 2 types of users (freelancers and customers), what is the best approach to get this done. Both users will have different view and info. Should I:
Create 2 different apps, and repeat the normal registeration and login for each.
If I do the above, hope the freelancers when logged in won't access customers view.
How do I add user type to the user profile if I decide to use one app and model for it.
Please I need a step by step beginner approach, or a link to relevant source.
Thanks.
You could try this:
class UserProfile(models.Model):
user = models.ForeignKey(User)
#define general fields
class Freelancer(models.Model):
profile = models.ForeignKey(UserProfile)
#freelancer specific fields
class Meta:
db_table = 'freelancer'
class Customers(models.Model):
profile = models.ForeignKey(UserProfile)
#customer specific fields
class Meta:
db_table = 'customer'
You can then have as many Users as you want from the UserProfile.
You should need just use Groups Django mechanism - you need to create two groups freelancer and let say common and check whether user is in first or second group - then show him appropriate view
To check whether user is in group you can use
User.objects.filter(pk=userId, groups__name='freelancer').exists()
You Could Try extending the Default Django Auth User like this
Create an App with Account or Whatever name you like , then in models.py write like below
class User(AbstractUser):
is_head = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_public = models.BooleanField(default=False)
Add Auth Extended Model in Settings.py
AUTH_USER_MODEL = 'accounts.User'
Migrate your Account app and you are all set with Your User Extended Model.

Unable to autopopulate a Django admin field using an attribute from a OneToOne relationship

There are similar questions to this, but I believe mine is different. I am very new to Django and to Python, so please forgive my ignorance.
I have a custom class UserProfile that inherits from the django.contrib.auth.models User class. This UserProfile is based on the exercise in Tango with Django, however, I am using the example to create a different project/app.
I have UserProfile linked to the standard User model with a OneToOneField relationship in my models.py, as shown below:
class UserProfile(models.Model):
# Links UserProfile to a User model instance.
user = models.OneToOneField(User)
# The additional attribute I wish to include that isn't in User.
slug = models.SlugField(unique=True)
In my admin.py file, I want an interface for UserProfile that I can work with, and I want the slugfield to autopopulate when I enter a new UserProfile. I want it to autopopulate based on the username attribute of User. However, I can't seem to make it work. Here is my admin.py code:
class UserProfileAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("user.username",)}
When I try to runserver from my command line, I get the following error:
ERRORS: <class 'climbcast.admin.UserProfileAdmin'>: (admin.E030) The
value of >'prepopula ted_fields["slug"][0]' refers to 'user.username',
which is not an attribute of >' climbcast.UserProfile'.
System check identified 1 issue (0 silenced).
It won't allow me to access the user.username attribute this way, even though I can access it that way in the python shell. Any ideas on how to make this work?
Unfortunately prepopulated_fields doesn’t accept DateTimeField, ForeignKey, nor ManyToManyField fields.
Source: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.prepopulated_fields
Possible solution, in your models.py (make slug attribute optional):
from django.utils.encoding import force_text
from django.template.defaultfilters import slugify
class UserProfile(models.Model):
[...]
slug = models.SlugField(blank=True, db_index=True, unique=True)
def get_unique_slug(self, value):
"""
Generate a valid slug for a for given string.
"""
qs = self.__class__.objects.all()
used = qs.values_list('slug', flat=True)
i = 1
baseslug = slugify(value)
while slug in used:
slug = '%s-%s' % (baseslug, i)
i += 1
return slug
def save(self, *args, **kwargs):
if not self.slug and self.user:
self.slug = self.get_unique_slug(force_text(self.user.username))
super(UserProfile, self).save(*args, **kwargs)

Categories