Adding user to group on creation in Django - python

I'm looking to add a User to a group only if a field of this User is specified as 'True' once the User is created. Every User that is created would have a 'UserProfile' associated with it. Would this be the correct way to implement such a thing?
models.py:
def add_group(sender, instance, created, **kwargs):
if created:
sender = UserProfile
if sender.is_in_group:
from django.contrib.auth.models import Group
g = Group.objects.get(name='Some Group')
g.user_set.add(sender)
post_save.connect(add_group, sender=UserProfile)
Thanks in advance!

Another option is using a post_save signal
from django.db.models.signals import post_save
from django.contrib.auth.models import User, Group
def add_user_to_public_group(sender, instance, created, **kwargs):
"""Post-create user signal that adds the user to everyone group."""
try:
if created:
instance.groups.add(Group.objects.get(pk=settings.PUBLIC_GROUP_ID))
except Group.DoesNotExist:
pass
post_save.connect(add_user_to_public_group, sender=User)
Only trouble you will have is if you use a fixture ... (hence the DoesNotExists .. )

try this:
def save(self, *args, **kwargs):
# `save` method of your `User` model
# if user hasnt ID - is creationg operation
created = self.id is None
super(YourModel, self).save(*args, **kwargs)
# after save user has ID
# add user to group only after creating
if created:
# adding to group here

Related

How to Update value of a Model with another Model's value in Django

class Trade(models.Model):
pips = models.FloatField(default=0)
direction = models.CharField(max_length=30)
new_balance = FloatField(default=0.0)
...
class Summary(models.Model):
winning_trades = models.IntegerField(default=0)
account_balance = FloatField(default=0.0)
...
When a user post a request he/she will populate the Trade model, this will update the summary model and send back to the user new summary data. How can I do this in an elegant way?
You're most likely looking for Django Signals. You'd want your Trade model's create event to trigger a post_save signal that a listener will receive and process.
Assuming you have saved your models in a file models.py,
Create a file signals.py with the following:
# code
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Trade, Summary
#receiver(post_save, sender=Trade)
def update_summary(sender, instance, created, **kwargs):
if created:
# query to update Summary as needed
You'll have to add the signals to your app config.
in your apps.py of the relevant app, add the following:
from django.apps import AppConfig
class AppnameConfig(AppConfig):
name = 'appname'
**def ready(self):
import appname.signals**
First, I will encourage you to create a transaction to perform these two actions. If the second one fails, your database will remain consistent.
Then, Django allows you to override the model methods such as save. You should try that with something like the following:
class Trade():
...
def save(self, *args, **kwargs):
with transaction.atomic():
super.save()
update_summary()
Then, in the view, you could query for the recently updated Summary and return it.
class TradeViewSet():
def create(self, request, *args, **kwargs):
user = request.user
trade = Trade.create(request.data)
updated_summary = get_summary(user.id)
response = SummarySerializer(updated_summary)
return Response(response)

Django: Programmatically add Groups on User save

After a user is saved, I need to make sure that its instance is associated with a group by default.
I have found two ways to achieve that:
Overriding the model's save() method
models.py:
from django.contrib.auth.models import AbstractUser, Group
class Person(AbstractUser):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
to_add = Group.objects.get(id=1) # get_or_create is a better option
instance.groups.add(to_add)
Capturing a post_save signal:
signals.py:
from django.conf import settings
from django.contrib.auth.models import Group
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(
post_save,
sender=settings.AUTH_USER_MODEL,
)
def save_the_group(instance, raw, **kwargs):
if not raw:
to_add = Group.objects.get(id=1) # get_or_create is a better option
instance.groups.add(to_add)
Are these methods equal in achieving their goal?
Is there a better one in Django terms of "Good Practice"?
Update
Acquiring a better understanding of how Django works, I see that the
confusion and also the solution lie in BaseModelForm.save():
...
if commit:
# If committing, save the instance and the m2m data immediately.
self.instance.save()
self._save_m2m()
...
and in BaseModelForm._save_m2m():
...
if f.name in cleaned_data:
f.save_form_data(self.instance, cleaned_data[f.name])
...
The instance is first saved to acquire a primary key (post_save
signal emmited) and then all its many to many relations are saved based
on ModelForm.cleaned_data.
If any m2m relation has been added during the post_save signal or at
the Model.save() method, it will be removed or overridden from
BaseModelForm._save_m2m(), depending on the content of the
ModelForm.cleaned_data.
The transaction.on_commit() -which is discussed as a solution in this
asnwer later on and in a few other SO answers from which I was inspired
and got downvoted- will delay the changes in the signal until
BaseModelForm._save_m2m() has concluded its operations.
Although, in some special cases the transaction.on_commit() is very useful, in this case is an overkill, not only because it is complexing the situation in
an awkward manner (the most suitable signal is m2m_changed as explained here) but because avoiding signals altogether, is rather
good.
Therefore, I will try to give a solution that caters for both occasions:
If the instance is saved from Django Admin (ModelForm)
If the instance is saved without using a ModelForm
models.py
from django.contrib.auth.models import AbstractUser, Group
class Person(AbstractUser):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if not getattr(self, 'from_modelform', False): # This flag is created in ModelForm
<add - remove groups logic>
forms.py
from django import forms
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.models import Group
from my_app.models import Person
class PersonChangeForm(UserChangeForm):
def clean(self):
cleaned_data = super().clean()
if self.errors:
return
group = cleaned_data['groups']
to_add = Group.objects.filter(id=1)
to_remove = Group.objects.filter(id=2)
cleaned_data['groups'] = group.union(to_add).difference(to_remove)
self.instance.from_modelform = True
return cleaned_data
class Meta:
model = Person
fields = '__all__'
This will either work with:
>>> p = Person()
>>> p.username = 'username'
>>> p.password = 'password'
>>> p.save()
or with:
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import get_user_model
from django.forms.models import modelform_factory
user_creationform_data = {
'username': 'george',
'password1': '123!trettb',
'password2': '123!trettb',
'email': 'email#yo.gr',
}
user_model_form = modelform_factory(
get_user_model(),
form=UserCreationForm,
)
user_creation_form = user_model_form(data=user_creationform_data)
new_user = user_creation_form.save()
Old answer
Based on either this or that SO questions along with an
article titled "How to add ManytoMany model inside a post_save
signal" the solution I turned to, is to use on_commit(func, using=None):
The function you pass in will be called immediately after a
hypothetical database write made where on_commit() is called would be
successfully committed.
from django.conf import settings
from django.contrib.auth.models import Group
from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
def on_transaction_commit(func):
''' Create the decorator '''
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
#receiver(
post_save,
sender=settings.AUTH_USER_MODEL,
)
#on_transaction_commit
def group_delegation(instance, raw, **kwargs):
to_add = Group.objects.get(id=1)
instance.groups.add(to_add)
The above code does not take into account that every login causes a
post_save signal.
Digging Deeper
A crucial point made in the relevant Django ticket is that the
above code will not work if a save() call is made inside an
atomic transaction together with a validation that depends on the
result of the group_delegation() function.
#transaction.atomic
def accept_group_invite(request, group_id):
validate_and_add_to_group(request.user, group_id)
# The below line would always fail in your case because the
on_commit
# receiver wouldn't be called until exiting this function.
if request.user.has_perm('group_permission'):
do_something()
...
Django docs describe in more details the constraints under which
on_commit() successfully works.
Testing
During testing, it is crucial to use the
TransactionTestCase or the
#pytest.mark.django_db(transaction=True) decorator when testing with pytest.
This is an example of how I tested this signal.

How to link two tables in django?

I use the built-in User model in django, but i want use some custom fields, i was create new model
class Profile(models.Model):
user = models.OneToOneField(User)
profile_img = models.ImageField(null=True)
def __str__(self):
return self.user.username
this is my custom model.
But when i create new user it doesn't display in admin(in profile table), i need to add user from admin panel, after adding it works fine. what i should to do to display Profile info of all users?
p.s. When i was create table profile and was try to select info from joined tabels, sql query wasn't return anything, but after adding existed user to Profile table sql query return all what i want
To create a new Profile object when a new user is created, you can use a pre_save receiver:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(
user=instance
)
The created parameter is to check if the object has been created ( created=True ) or updated ( created=False )
I am not sure to understand what you want... If you want to manage profile in admin app you have to register it.
from django.contrib import admin
from myproject.myapp.models import Profile
class ProfileAdmin(admin.ModelAdmin):
pass
admin.site.register(Profile, ProfileAdmin)
Edit: you can use a signal to automatically create a profile when creating a user.
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from myproject.myapp.models import Profile
#receiver(post_save, sender=User)
def create_profile(sender, instance **kwargs):
Profile.objects.create(user=instance)
Note that sometime using signals may be hard to maintain. You can also use AbstractBaseUser. This is a classical issue, which is widely covered in a lot of posts.
One I particularly like: https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html

How to use Django Signals to run a function after user registration completed?

I'm using django_registration_redux package to handle registering new users. Then I have a set of categories, which I want each new user to follow them by default. So I have to run following code immediately after a user object created:
for category in categories:
f = Follow(user=user.username, category=category)
f.save()
After reading django Docs, I guessed that adding following method to the UserProfile model would work:
def follow_def_cat(sender, instance, created, **kwargs):
if created:
for category in categories:
f = Follow(user=instance.username, category=category)
f.save()
post_save.connect(follow_def_cat, sender=User)
But it seems I couldn't connect saving user signal to the function.
Put your connect instruction out of the signal method.
def follow_def_cat(sender, instance, created, **kwargs):
if created:
for category in categories:
f = Follow(user=instance.username, category=category)
f.save()
post_save.connetc(follow_def_cat, sender=User)
And remember that follow_def_cat is not a model method, you should create it at the same level that the model class:
class UserProfile(models.Model):
...
def follow_def_cat(sender, ...):
...
post_save.connect(follow_def_cat, sender=User)

Django Groups not updating when saving User

I'm trying to add users that belong to staff to the staff group on change. I've tried it with signals and by overloading save in the User model but neither of them seem to work. Does django has any limitations I'm not aware of when saving?
Here are both code snippets I tried to work with. User is inherited from the AbstractUser and used with the new Django 1.5 feature AUTH_USER_MODEL = 'app.User', Groups are standard auth groups.
class User(AbstractUser):
def save(self, force_insert=False, force_update=False, **kwargs):
if self.is_staff:
if not self.groups.filter(name='staff').exists():
g = Group.objects.get(name='staff')
g.user_set.add(self)
else:
if self.groups.filter(name='staff').exists():
g = Group.objects.get(name='staff')
g.user_set.remove(self)
super(User, self).save(force_insert, force_update)
#receiver(post_save, sender=User)
def my_handler(sender, **kwargs):
user = kwargs['instance']
if user.is_staff:
if not user.groups.filter(name='staff').exists():
g = Group.objects.get(name='staff')
g.user_set.add(user)
else:
if user.groups.filter(name='staff').exists():
g = Group.objects.get(name='staff')
g.user_set.remove(user)
If you use a form(ModelForm) then the reason for disappearing group might be a form.save_m2m() that is called after your user.save(). For example, if you have an empty "groups" field in your form, save_m2m just removes all groups from you user.

Categories

Resources