Django grant permissions on/ after saving object - django

I have a Django model which includes specific permissions. I want to be able to assign permissions to those users in the assigned_to field. I am using django-guardian to manage the permissions.
from django.db import models
from django.contrib.auth.models import User
from guardian.shortcuts import assign_perm
class Project(models.Model):
...
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
created_by = models.ForeignKey(
User,
related_name="%(app_label)s_%(class)s_related"
)
permissions = (
('view_project', 'View project'),
)
I have tried to implement a custom save method. If I try and do it before the save:
def save(self, *args, **kwargs):
for user in self.assigned_to:
assign_perm('view_project', user, self)
super(Project, self).save(*args, **kwargs)
I get an error:
ObjectNotPersisted: Object <project_name> needs to be persisted first
If I do it after the save (which I guess is wrong anyhow):
def save(self, *args, **kwargs):
super(Project, self).save(*args, **kwargs)
for user in self.assigned_to:
assign_perm('view_project', user, self)
I get an Type Error 'ManyRelatedManager' object is not iterable.
Should I be using a post-save signal for this? What is the best approach for what I assume is a common pattern?

The error is caused because the field itself is not iterable, you need to specify the queryset using filter or all:
for user in self.assigned_to.all():
assign_perm('view_project', user, self)
However, as you commented, this needs to be done after the parent model instance is saved, so yes you can either create a post_save signal to accomplish this or save the M2M relations in your view after your call to save the parent instance.

Related

I got a query error when calling the user login in django

views.py
from .models import Profile
#login_required(login_url='/signin')
def settings(request):
user_profile=Profile.objects.get(user=request.user)
return render(request,'setting.html',{'user_profile':user_profile})
I think the error is in :user_profile=Profile.objects.get(user=request.user)
but I don't know why
models.py
from django.contrib.auth.models import User
from django.db import models
class Profile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
id_user = models.IntegerField()
bio = models.TextField(blank=True)
profileimg = models.ImageField(upload_to='profile_pics/', default='default-profile.png')
location = models.CharField(max_length=300, blank=True)
birth_date = models.DateField(null=True, blank=True)
ERROR
There may be a User instance for the user you are trying to log in as, however that does not mean there is a Profile present as well. You need to make sure every User has a profile. There are a few ways to do it:
(Recommended) Override the save() method on User to automatically create a Profile when a new user is created. e.g:
class User(..):
# user properties
def save(self, *args, **kwargs):
if not self.pk:
Profile.objects.create(user=user)
super(MyModel, self).save(*args, **kwargs)
Handle the action on the API call. So in your function you can check if the user has a profile, if they don't you may wish to create one. For example:
def settings(request):
user_profile=Profile.objects.get_or_create(user=request.user)
return render(request,'setting.html',{'user_profile':user_profile})
Here I used get_or_create, but you are free to use try/except blocks or whatever suits your backend logic best!

Am I overriding the save method on the model in a wrong way in django?

I have a Profile model that extends the user model like so,
class Profile(User):
user = models.OneToOneField(User, parent_link=True, on_delete=models.CASCADE)
slug = models.SlugField(unique=True, blank=True)
def save(self, *args, **kwargs):
print('self.username')
print(self.username)
self.slug = self.username
super(Profile, self).save(*args, **kwargs)
I am trying to create a slug field for my model , so I override the save method to include the slug as the username. The thing is, when I create a new user with the command createsuperuser and print out the username as you can see in the code, it doesn't show anything - it doesn't show the provided username. Could this be the reason why I am having this problem ? And if so, how can I fix it?
If you're sure you don't want to have your user's profile data in a separate model with a OneToOneField back to Django's default User, then you should probably subclass AbstractUser and not User, as specified in the docs.
https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#substituting-a-custom-user-model
Consider this part :
If you’re entirely happy with Django’s User model and you just want to add some additional profile information, you could simply subclass django.contrib.auth.models.AbstractUser and add your custom profile field [...]
(from https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#extending-django-s-default-user)
Then you'd go like this:
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager
from django.utils.text import slugify
from django.db import models
class User(AbstractUser):
slug = models.SlugField(unique=True, blank=True)
objects = UserManager()
def save(self, *args, **kwargs):
print('self.username')
print(self.username)
self.slug = slugify(self.username)
super().save(*args, **kwargs)
Then, define settings.AUTH_USER_MODEL,
AUTH_USER_MODEL = "myapp.User" # also, add 'myapp' to INSTALLED_APPS
Remember: you'll need to reference your User model like this (and this is how you should do it anyway, whether you customized that model or not):
from django.contrib.auth import get_user_model
User = get_user_model()
Profile cannot inherit User but instead should inherit Model. And, for the creation of User to be able to create a corresponding row in a different table (Profile) and set the slug will involve the use of signals.
You are overriding the save method correctly. You need to do more when you use the createsuperuser command.
from django.utils.text import slugify
from django.contrib.auth.models import UserManager
class CustomUserManager(UserManager):
def _create_user(self, username, email, password, **extra_fields):
username_slug = slugify(username)
extra_fields.setdefault('slug', username_slug)
super()._create_user(username, email, password, **extra_fields)
class Profile(User):
user = models.OneToOneField(User, parent_link=True, on_delete=models.CASCADE)
slug = models.SlugField(unique=True, blank=True)
def save(self, *args, **kwargs):
print('self.username')
print(self.username)
self.slug = slugify(self.username)
super().save(*args, **kwargs)
objects = CustomUserManager()

Update the instance after save, using signals by conditionally refference back to the instance in some cases

I have the following abstract class:
class UserStamp(models.Model):
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True,
related_name='%(app_label)s_%(class)s_created_by', on_delete=models.CASCADE)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
related_name='%(app_label)s_%(class)s_updated_by', on_delete=models.CASCADE)
class Meta:
abstract = True
I have a custom User that inherits from User.
class User(AbstractBaseUser,PermissionsMixin, UserStamp):
account = models.ForeignKey(Account, blank=True, null=True, related_name='owner',on_delete=models.CASCADE)
The User can create/update himself or by other user.
When the user create/update himself I don't have anything for created_by, update_by.
The user can be created using Django Admin or outside Django Admin;
In Django Admin the user can be created by staff, outside Django is self created;
Also there is superuser that is created in terminal;
Regarding the update the user in both Django Admin and outside can be self updated or by another user.
I thought on using post_save or a custom signal. The issue is that request.user is not available in the model, but in View, and controlling the View in Admin and also in terminal(superuser) is a bottleneck.
Maybe trying to do a query after save passing the instance, but I don't exactly know how to combine all of them signal/query, check superuser.
Create your own custom signals.
signals.py
from django.dispatch import Signal
# you can pass any number of arguments as per your requirement.
manage_user = Signal(providing_args=["user", "is_updated"])
def user_handler(sender, **kwargs):
# `user` who created/updated object i.e `request.user`
user = kwargs['user']
# `is_updated` will be `False` if user object created.
is_updated = kwargs['is_updated']
# `sender` is the user object which is created/updated.
...
# do your stuff
manage_user.connect(user_handler)
models.py
Override save() method of your custom user class.
from .signals import manage_user
class User(...):
...
# call this save method like obj.save(created_by=request.user)
def save(self, created_by, *args, **kwargs):
super().save(*args, **kwargs)
# is_updated will be True if user object is updated
manage_user.send(sender=self, user=created_by, is_updated=True if self.id else False)
send manage_user signal when user model changed, just like post_save signal,
but now you have control over parameters.
UPDATE
If you are using django admin to create user you can overide save_model, you have request object there.
from django.contrib import admin
#admin.register(User)
class UserAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
super(UserAdmin, self).save_model(request, obj, form, change)
manage_user.send(sender=self, user=request.user, is_updated=True if self.id else False)

Django model save related model

I have three models, Person, Role and Student. Person is related to role with ManyToManyField and Student inherits from Person. The code:
class Person(models.Model):
#fields
class Role(models.Model):
ROLE_CHOICES = (
('AL', 'ALUMNO'),
#... more choices
)
person = models.ManyToManyField(Person)
role = models.CharField(
'Rol',
max_length = 2,
choices = ROLE_CHOICES
)
class Student(Person):
# fields
def save(self, *args, **kwargs):
super(Student, self).save(*args, **kwargs)
# what code should I put here to save role?
I want to, on saving a Student, automatically save a role for him/her ('AL'). Also it has to execute on create and not in update.
I've seen other posts addresing this, but it remains unclear to me how
to implement this.
As I understand, I can override the save method, but I'm not sure how exactly do this. I'm aware that post_save signal can also accomplish this, but I'm not sure how either.
Thanks.
You can't do this before student get it's pk by save method,because m2m relations is establish by your instance pk and pk is generate after your instance save to db.
two way to archieve:
First one:
new a signals.py file in your app:
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import *
#receiver(post_save, sender=Student)
def create_student(sender, instance=None, created=False, **kwargs):
if created:
role, is_created = Role.objects.get_or_create(name='AL')
role.person.add(instance)
and in your apps.py
class UserConfig(AppConfig):
name = 'user'
def ready(self):
import user.signals
replace User with your own app_label.
Second one:
after django 1.9,django has transaction tool allow you performing actions after commit.doc is here:
from django.db import transaction
class Student(Person):
.
.
.
def save(self, *args, **kwargs):
instance = super(Student, self).save(*args, **kwargs)
if not self.pk:
# do when create
transaction.on_commit(self.update_role)
return instance
def update_role(self):
# this will be call after instance save to db
role, is_created = Role.objects.get_or_create(name='AL')
role.person.add(self)
all code is untested.

Django - UserProfile m2m field in admin - error

My models:
class UserProfile(models.Model):
TYPES_CHOICES = (
(0, _(u'teacher')),
(1, _(u'student')),
)
user = models.ForeignKey(User, unique=True)
type = models.SmallIntegerField(default=0, choices=TYPES_CHOICES, db_index=True)
cities = models.ManyToManyField(City)
class City(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50)
In admin.py:
admin.site.unregister(User)
class UserProfileInline(admin.StackedInline):
model = UserProfile
class UserProfileAdmin(UserAdmin):
inlines = [UserProfileInline]
admin.site.register(User, UserProfileAdmin)
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
"""Create a matching profile whenever a user object is created."""
if created:
profile, new = UserProfile.objects.get_or_create(user=instance)
But when I add new user and select a city I get that error: IntegrityError at /admin/auth/user/add/
(1062, "Duplicate entry '3' for key 'user_id'")
What is wrong with my code? If I don't select any city - user is added properly. Some way, user is being added to UserProfile more than once.
I had this same issue recently. It actually makes perfect sense when you think about it. When you save a form with inlines in the admin, it saves the main model first, and then proceeds to save each inline. When it saves the model, your post_save signal is fired off and a UserProfile is created to match, but now it's time to save the inlines. The UserProfile inline is considered new, because it didn't exist previously (has no pk value), so it tries to save as an entirely new and different UserProfile and you get that integrity error for violating the unique constraint. The solution is simple. Just override UserProfile.save:
def save(self, *args, **kwargs):
if not self.pk:
try:
p = UserProfile.objects.get(user=self.user)
self.pk = p.pk
except UserProfile.DoesNotExist:
pass
super(UserProfile, self).save(*args, **kwargs)
Essentially, this just checks if there's an existing UserProfile for the user in question. If so, it sets this UserProfile's pk to that one's so that Django does an update instead of a create.