I have a celery task that uses send_mail to send an email notification to a User after they create an account.
from celery import shared_task
from celery.utils.log import get_task_logger
from django.core.mail import send_mail
from my_group.settings import NOTIFICATION_EMAIL
logger = get_task_logger(__name__)
#shared_task(name='registration_notification_task')
def registration_notification_task(email):
logger.info('sent registration email')
send_mail(
'Welcome!',
'Message',
NOTIFICATION_EMAIL,
[email],
fail_silently=True
)
It takes in one argument of email and is called in my UserRegistration view:
class UserRegistration(generic.CreateView):
form_class = RegisterForm
template_name = 'registration/registration.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
user = form.save()
form.registration_notification()
login(self.request, user, backend='django.contrib.auth.backends.ModelBackend')
return redirect(self.success_url)
registration_notification() is defined in my RegisterForm here:
class RegisterForm(UserCreationForm):
email = forms.EmailField()
first_name = forms.CharField(max_length=100)
last_name = forms.CharField(max_length=100)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
def registration_notification(self):
email = self.cleaned_data['email']
if self.is_valid():
registration_notification_task.delay(email)
My question is whether it's better to call the task in the form when is_valid() or if it's better to just call the task directly in the view in def form_valid()? Is there a difference where the task is called?
Another reason I ask this is because it's easier for me to have the tasks be called in the views instead of passing arguments over to the form. Some tasks I want to run involve objects and when I try and pass instance from instance = form.save() into my form I get an error that object is not JSON serializable.
I've found ways around that, but I still want to know the preferred method for task placement. I'd personally rather always call them in the view if possible.
Related
on a Django project that uses django-taggit (https://pypi.org/project/django-taggit/)
I would like to make tags on a per user basis, this way each user can define its own set of tags.
I'm settings up the following model:
# models.py
from django.db import models
from taggit.models import Tag
from django.contrib.auth.models import User
# Create your models here.
class MyTag(Tag):
""" You must make taggit.models.Tag an abstract model"""
user = models.ForeignKey(User, related_name="to_tags", on_delete=models.SET_NULL)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def save(self, args, kwargs):
user = kwargs.pop('user')
self.user = user
super(MyTag, self).save(*args, **kwargs)
then to manage I'd use a normal form ( in this case the tags are for a Photo model)
# forms.py
class PhotoForm(forms.ModelForm):
class Meta:
model = Photo
fields = ('name', 'artistic_name', 'description', 'tags', 'is_top', 'note')
widgets = {
'description': forms.Textarea(attrs={'rows': 2}),
'note': forms.Textarea(attrs={'rows': 2})
}
now the Question...how to I save the user in the MyTag model?
I have to pass it to the form instance in the view doing something like:
def photo_detail(request, photo_id):
...
form = PhotoForm(request.POST or None, user=request.user)
...
if request.method == 'POST':
if form.is_valid():
form.save()
...
first question...should I pass the user when I make the form instance, or when I call the save method...?
Then I would have to intercept the Tag.save()...but here I'm lost.
Any help is appreciated.
Thank you very much!
Carlo
I am reading the Django documentation and ran across a couple examples in the forms sections that I do not understand why they did something 2 separate ways..
In the first example I found they send email from the FBV. This makes a lot of sense to a beginner:
from django.core.mail import send_mail
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
cc_myself = form.cleaned_data['cc_myself']
recipients = ['info#example.com']
if cc_myself:
recipients.append(sender)
send_mail(subject, message, sender, recipients)
return HttpResponseRedirect('/thanks/')
In the second example I found they use CBVs and apply methods to the form and call this method in the view:
from django import forms
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
# send email using the self.cleaned_data dictionary
pass
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)
When should you apply form methods and call those methods in the view? Is there a benefit to writing the code this way?
Also, in the second example they used pass in the send_email() method. I wanted to investigate further so I applied this to simple invite form I had..
class InviteForm(forms.Form):
name = forms.CharField()
phone = PhoneNumberField(help_text='Format must be: +15595551234', required=False)
email = forms.EmailField()
subject = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
send_mail(self.subject, self.message, 'someone#company.com', [self.email])
class InviteView(FormView):
template_name = 'invite/invite_form.html'
form_class = InviteForm
success_url = reverse_lazy('overview')
def form_valid(self, form):
form.send_email()
return super().form_valid(form)
Issue here is that I get:
AttributeError at /invite/
'InviteForm' object has no attribute 'subject'
Do I have to create parameters and pass them as arguments in the view after pulling the forms cleaned_data? If so, what is the point of creating the form method?
There isn't really a "correct" way of doing this. One could argue for doing this in the view but there are also good arguments to be made why send_mail should be part of the form.
Personally I like to keep my views as concise as possible. This is the first thing you read when you try to understand the behavior of the app. By just writing form.send_mail() it's clear that an email will be sent based on the data passed to the form. How this is done is nicely hidden and not really of concern to the view.
If you then later want to change what gets sent in the email, all the functionality is in one place, the form: the fields to populate and the send_mail functionality.
I have setup an override for Django-Registration-Redux to save on an additional model UserProfile below:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
tel_number = models.CharField(max_length=20, null = True)
This is setup with the following form:
class UserProfileRegistrationForm(RegistrationFormUniqueEmail):
tel_number = forms.CharField()
And regbackend.py:
from registration.backends.default.views import RegistrationView
from .forms import UserProfileRegistrationForm
from .models import UserProfile
class MyRegistrationView(RegistrationView):
form_class = UserProfileRegistrationForm
def register(self, request, form_class):
new_user = super(MyRegistrationView, self).register(request, form_class)
user_profile = UserProfile()
user_profile.user = new_user
user_profile.tel_number = form_class.cleaned_data['tel_number']
user_profile.save()
return user_profile
However, even though the view loads correctly and I can fill it out, I get the following error each time I save:
register() missing 1 required positional argument: 'form_class'
I assume this is something passing incorrectly?
I checked the code quickly here:
https://github.com/macropin/django-registration/blob/master/registration/backends/default/views.py
That Django-Registration-Redux seems to implement in RegistrationView, the register method like so:
def register(self, form):
#code
Try to remove request and it should work, sth like this.
def register(self, form_class):
new_user = super(MyRegistrationView, self).register(form_class)
#code
I'm been using the default user model in django for quite a abit and I realize , if I need to further enhance it , I would have to create my own custom User Model in django 1.5 .
I created my custom user model and I have a function which allows users to sign in .
I think my custom user model is incompatible with my function because it wouldn't allow me to do request.user . How can I fix this so I can use request.user again?
views
def LoginRequest(request):
form = LoginForm(request.POST or None)
if request.user.is_authenticated():
username = User.objects.get(username=request.user)
url = reverse('world:Profile', kwargs = {'slug': person.slug})
return HttpResponseRedirect(url)
if request.POST and form.is_valid():
user = form.authenticate_user()
login(request, user)
username= User.objects.get(username=request.user)
person = Person.objects.get(user=request.user)
url = reverse('world:Profile', kwargs = {'slug': person.slug})
return HttpResponseRedirect(url)
return render(request, 'login.html',{'form': form})
models
class PersonManager(BaseUserManager):
def create_user(self, email,date_of_birth, username,password=None,):
if not email:
msg = 'Users must have an email address'
raise ValueError(msg)
if not username:
msg = 'This username is not valid'
raise ValueError(msg)
if not date_of_birth:
msg = 'Please Verify Your DOB'
raise ValueError(msg)
user = self.model(
email=PersonManager.normalize_email(email),username=username,date_of_birth=date_of_birth)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self,email,username,password,date_of_birth):
user = self.create_user(email,password=password,username=username,date_of_birth=date_of_birth)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class Person(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(verbose_name='email address',max_length=255,unique=True,db_index=True,)
username = models.CharField(max_length=255, unique=True)
date_of_birth = models.DateField()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'date_of_birth',]
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
objects = PersonManager()
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __unicode__(self):
return self.email
The problem is that User refers to django.contrib.auth.models.User and now you have got a Custom User pet.Person assuming you have in the settings.py
AUTH_USER_MODEL = "pet.Person"
you have to define User with the Custom User model and you can do this with get_user_model at the top of the file where you use User
from django.contrib.auth import get_user_model
User = get_user_model()
now you will be able to use Custom User model and the problem has been fixed.
For anyone else who might come across this problem, I also solved it by simply doing this on forms.py:
add this at the top of the forms.py file
from .models import YourCustomUser
and then add this to your forms.py CustomUser form:
class SignUpForm(UserCreationForm):
#profile_year = blaaa blaa blaaa irrelevant.. You have your own stuff here don't worry about it
# here is the important part.. add a class Meta-
class Meta:
model = YourCustomUser #this is the "YourCustomUser" that you imported at the top of the file
fields = ('username', 'password1', 'password2', #etc etc, other fields you want displayed on the form)
BIG NOTES, ATTENTION:
This code worked for my case. I have a view for signing users up, I had a problem here and I solved it, I haven't tried it for logging in users.
The include = () part is required, or you can add exclude = (), but you have to have one
Important caveat to update the above solutions...
If you're facing this kind of problem, you've probably tried various solutions around the web telling you to add AUTH_USER_MODEL = users.CustomUser to settings.py and then to add the following code to views.py forms.py and any other file that calls User:
from django.contrib.auth import get_user_model
User = get_user_model()
And then you scratch your head when you get the error:
Manager isn't available; 'auth.User' has been swapped for 'users.User'
Anytime your code references User such as:
User.objects.get()
Cause you know you already put objects = UserManager() in your custom user class (UserManager being the name of your custom manager that extends BaseUserManager).
Well as it turns out doing:
User = get_user_model() # somewhere at the top of your .py file
# followed by
User.objects.get() # in a function/method of that same file
Is NOT equivalent to:
get_user_model().objects.get() # without the need for User = get_user_model() anywhere
Perhaps not intuitive, but it turns out that that in python, executing User = get_user_model() once at the time of import does not then result in User being defined across subsequent calls (i.e. it does not turn User into a "constant" of sorts which you might expect if you're coming from a C/C++ background; meaning that the execution of User = get_user_model() occurs at the time of imports, but is then de-referenced before subsequent called to class or function/method in that file).
So to sum up, in all files that reference the User class (e.g. calling functions or variables such as User.objects.get() User.objects.all() User.DoesNotExist etc...):
# Add the following import line
from django.contrib.auth import get_user_model
# Replace all references to User with get_user_model() such as...
user = get_user_model().objects.get(pk=uid)
# instead of user = User.objects.get(pk=uid)
# or
queryset = get_user_model().objects.all()
# instead of queryset = User.objects.all()
# etc...
Hope this helps save others some time...
In forms.py
# change
from django.contrib.auth.models import User
# to
from django.contrib.auth import get_user_model
Then add the following code at the top
User = get_user_model()
All the solutions provided above did not work in my case. If you using Django version 3.1 there is another solution for you:
In auth/forms, comment out line 10 and change the model in line 104 & 153 to your defined model.
I'm trying to set up my custom user model in Django. The reason is that I want to use email as the username, and remove the username field entirely. I've run into a error, that I just can't figure out.
Manager isn't available; User has been swapped for 'app.MyUser'
Exception Location: .../django/db/models/manager.py in __get__, line 256
Python Version: 2.7.3
Python Path:
[...project specific files,
'/usr/lib/python2.7',
'/usr/lib/python2.7/plat-linux2',
'/usr/lib/python2.7/lib-tk',
'/usr/lib/python2.7/lib-old',
'/usr/lib/python2.7/lib-dynload',
'/usr/local/lib/python2.7/dist-packages',
'/usr/lib/python2.7/dist-packages',
'/usr/lib/python2.7/dist-packages/PIL',
'/usr/lib/python2.7/dist-packages/gtk-2.0',
'/usr/lib/pymodules/python2.7',
'/usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode']
I've googled like crazy, but haven't found too many pages about this error message. I have found some pages, with suggestions on how to solve it, but none of the suggestions have worked for me.
My code: I've set the custom user model. I have declared the custom user model AUTH_USER_MODEL = 'app.MyUser' in settings.py. I have also set up a custom UserManager:
class MyUserManager(BaseUserManager):
def create_user(self, email, password=None):
"""
Creates and saves a User with the given email. Note that none of the optional fields gets values in the creation. These fields will have to be filled out later on.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(email=MyUserManager.normalize_email(email))
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None):
"""
Creates and saves a superuser with the the above mentioned attributes
"""
user = self.create_user(email, password=password)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser, PermissionsMixin):
"""
Custom made User model. No username, instead email is used as unique field and index
"""
Genders = (('M', 'Man'), ('K', 'Woman'))
FirstName = models.CharField(max_length=30)
LastName = models.CharField(max_length=40)
Gender = models.CharField(max_length=2, choices=Genders, default='K')
email = models.EmailField(verbose_name='email address', max_length=255, unique=True, db_index=True,)
twitter = models.CharField(max_length=30)
is_admin = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def get_full_name(self):
# The user is identified by their email address
return self.email
def get_short_name(self):
# The user is identified by their email address
return self.email
def __unicode__(self):
return self.email
objects = MyUserManager()
I've tried to declare to different types of UserAdmins, none of which is making any difference,the first one I tried was;
class MyUserAdmin(UserAdmin):
# The forms to add and change user instances
#form = UserChangeForm
#add_form = FrontpageRegistrationForm
list_display = ('email', 'FirstName', 'LastName', 'Gender', 'twitter')
list_filter = ()
add_fieldsets = ((None, {'classes': ('wide',),'fields': ('email', 'password1', 'password2')}),)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ()
admin.site.register(MyUser, MyUserAdmin)
I've commented out the two attributes add_form and form because they raised some form errors I wanted to get back to at a later point.
The second UserAdmin was made, after reading about a possible fix here. This didn't help the situation though;
class MyUserAdmin(admin.ModelAdmin):
# The forms to add and change user instances
#form = UserChangeForm
add_form = FrontpageRegistrationForm
add_fieldsets = ((None, {'classes': ('wide',),'fields': ('email', 'password1', 'password2')}),)
def get_fieldsets(self, request, obj=None):
if not obj:
return self.add_fieldsets
return super(MyUserAdmin, self).get_fieldsets(request, obj)
def get_form(self, request, obj=None, **kwargs):
defaults = {}
if obj is None:
defaults.update({'form': self.add_form,'fields': admin.util.flatten_fieldsets(self.add_fieldsets),})
defaults.update(kwargs)
return super(MyUserAdmin, self).get_form(request, obj, **defaults)
I've also tried deleting all tables in the db with no luck.
I would be eternally greatful to anyone who even looks at the problem. And if any one were to solve this, I would try my best to talk my wife into naming our firstborn after the Avatar that gave me a solution so that I could go on living my life.
EDIT:
I tried setting the AUTH_USER_MODELto mainfolder.app.MyUserI'm sure the "mainfolder" is on the pythonpath. init.py in the app should be correct. The new settings.py gave the following server error; auth.user: AUTH_USER_MODEL is not of the form 'app_label.app_name'.admin.logentry: 'user' has a relation with model smartflightsearch.SFSdb.MyUser, which has either not been installed or is abstract.registration.registrationprofile: 'user' has a relation with model, which has either not been installed or is abstract. A new clue I don't know how to interpret..
TL;DR: Use the code from the Solution part at the end of the following answer.
Longer explanation: You see, as of Django 1.5, it's not enough to subclass Django's UserAdmin to be able to interact with swappable user models: you need to override respective forms as well.
If you jump to django.contrib.auth.admin source, you'll see that the UserAdmin's form and add_form have these values:
# django/contrib/auth/admin.py
class UserAdmin(admin.ModelAdmin):
...
form = UserChangeForm
add_form = UserCreationForm
Which point us to forms in django.contrib.auth.forms that do not respect swappable user models:
# django/contrib/auth/forms.py
class UserCreationForm(forms.ModelForm):
...
class Meta:
model = User # non-swappable User model here.
class UserChangeForm(forms.ModelForm):
...
class Meta:
model = User # non-swappable User model here.
Solution: So, you should follow a great already existing answer (don't forget to vote it up!) which boils down to this:
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
class MyUserChangeForm(UserChangeForm):
class Meta:
model = get_user_model()
class MyUserCreationForm(UserCreationForm):
class Meta:
model = get_user_model()
class MyUserAdmin(UserAdmin):
form = MyUserChangeForm
add_form = MyUserCreationForm
admin.site.register(MyUser, MyUserAdmin)
Hopefully, this would be fixed in the future releases of Django (here's the corresponding ticket in the bug tracker).
When you said you set AUTH_USER_MODEL = 'app.MyUser' I'm assuming your app where is located the MyUser class, have a structure, perharps, like this:
inside the app/ dir: init.py and models.py and stuff..
so inside the models.py you have the MyUser and inside the init.py:
from models import MyUser