Saving profile with registration in Django-Registration - django

In Django-Registration it says you can save a custom profile when you save a user.
But I have no idea what the documentation is asking me to do. Here is what they say:
To enable creation of a custom user profile along with the User (e.g., the model specified in the AUTH_PROFILE_MODULE setting), define a function which knows how to create and save an instance of that model with appropriate default values, and pass it as the keyword argument profile_callback. This function should accept one keyword argument:
user
The User to relate the profile to.
Can someone give me an example of the function that needs to be created and how to pass it as a argument?

You can pass the callback function in your urls.py file.
from mysite.profile.models import UserProfile
url( r'^accounts/register/$', 'registration.views.register',
{ 'profile_callback': UserProfile.objects.create }, name = 'registration_register' ),
Substitute your own function for UserProfile.objects.create as needed.

This is covered in this blogpost and expanded on in my answer to another question on the same issue
django-registration sends a signal at various events happening - registration and activation. At either of those points you can create a hook to that signal which will be given the user and request objects - from there you can create a profile for that user.
The signal from django-registration
#registration.signals.py
user_registered = Signal(providing_args=["user", "request"])
Code to create profile
#signals.py (in your project)
user_registered.connect(create_profile)
def create_profile(sender, instance, request, **kwargs):
from myapp.models import Profile
#If you want to set any values (perhaps passed via request)
#you can do that here
Profile(user = instance).save()

For anyone who met this problem, I think this blog post is a good tutorial: http://johnparsons.net/index.php/2013/06/28/creating-profiles-with-django-registration/.

Related

How to subclass registration form in django-registration

I wish to have the user agree to a TOS and to also support unique emails. django-registration has two different subclassed registration forms that does this: RegistrationFormTermsOfService and RegistrationFormUniqueEmail.
Do I have to make my own sublcass of RegistrationForm and then provide both those features? If so, how would this be accomplished? Would the registration form live inside my app's forms.py or somewhere else?
A quick look at the source for the two classes shows:
class RegistrationFormTermsOfService(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which adds a required checkbox
for agreeing to a site's Terms of Service.
"""
tos = forms.BooleanField(widget=forms.CheckboxInput,
label=_(u'I have read and agree to the Terms of Service'),
error_messages={'required': _("You must agree to the terms to register")})
class RegistrationFormUniqueEmail(RegistrationForm):
"""
Subclass of ``RegistrationForm`` which enforces uniqueness of
email addresses.
"""
def clean_email(self):
"""
Validate that the supplied email address is unique for the
site.
"""
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError(_("This email address is already in use. Please supply a different email address."))
return self.cleaned_data['email']
As you can see these two classes don't overwrite methods defined by the other so you should be able to just define your own class as being:
from registration.forms import RegistrationFormUniqueEmail, RegistrationFormTermsOfService
class RegistrationFormTOSAndEmail(RegistrationFormUniqueEmail, RegistrationFormTermsOfService):
pass
And it should function, however I have not tested this. As to where to place this class; forms.py is a good location.
Update:
A little reading at https://django-registration.readthedocs.org/en/latest/views.html which tells us that we can pass the view some parameters via the url definition; for instance a form class.
Simply use a URL like:
url(r'^register/$',
RegistrationView.as_view(form_class=RegistrationFormTOSAndEmail),
name='registration_register')

Adding custom variables into the request object in Django Middleware without using request.session

Is there a recommended way to pass a variable to all my views? Namely in my case, I want to pass a UserProfile object that Foreign Keys a django.contrib.auth.models.User object. I find most if not all my views need to pull the UserProfile object and putting it in Middleware seems like the way to go. It seems like I could do something like the following (I've seen a couple of solutions online that suggest it):
request.session['userprofile'] = userprofile_object
I don't like this because if my UserProfile model ever has a non-serializable field, it would break request.session.
If you have the AuthenticationMiddleware enabled, you will have a user object in all your views. To get the profile all you need to do is call user.get_profile in your view. For example, to output the id of the profile, you would do {{ user.get_profile.id }}.
If you would prefer not to call the get_profile function of the user object each time, you can add arbitrary items to your request. You would create a new middleware which would simply set
request.user_profile = request.user.get_profile()
Then just register that middleware in your settings.py and you should be good to go. I have used this method in the past for getting user geolocation data pinned to the request object.
This proposal depends on the assumption that userprofile objects only matter when users are already logged in so you can get the logged in user via request.user.
It should be possible to get the userprofile by travelling the foreignkey key relation in reverse like this:
if request.user.is_authenticated():
request.user.userprofile_object_set.all() #gets all related userprofile objects
else:
#...

Access to related data of newly created model instance using post_save signal handler

I need to send an e-mail when new instance of Entry model is created via admin panel. So in models.py I have:
class Entry(models.Model):
attachments = models.ManyToManyField(to=Attachment, blank=True)
#some other fields
#...
sent = models.BooleanField(editable=False, default=False)
Then I'm registring post_save handler function:
def send_message(sender, instance, **kwargs):
if not instance.sent:
#sending an e-mail message containing details about related attachments
#...
instance.sent = True
instance.save()
post_save.connect(send_message, sender=Entry)
It works, but as I mentioned before, I also need to access related attachments to include their details in the message. Unfortunatelly instance.attachments.all() returns empty list inside send_message function even if attachments were actually added.
As I figured out, when the post_save signal is sent, related data of saved model isn't saved yet, so I can't get related attachments from that place.
Question is: am I able to accomplish this using signals, or in any other way, or do I have to put this email sending code outside, for example overriding admin panel change view for Entry model?
Maybe you could use the M2M Changed Signal instead? This signal is sent when the M2M field is changed.
You should be able to do this by overriding the save_model() method on the ModelAdmin. You could either send your email in there or fire a custom signal which triggers your handler to send the email.
If you have inlines, I believe you need to use save_formset() instead.
I tried to use ModelAdmin save_model() method, as shadfc proposed.
Anyway newly changed related objects aren't accessible from there either. But save_model takes filled form as a parameter, so I used that. My send_message isn't used as a signal handler anymore and I added related_data parameter.
def send_message(sender, instance, related_data={}):
#sending e-mail using related_data parameter to access additional related objects
#...
in admin.py I have:
class EntryAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.save()
send_message(sender=Entry, instance=obj,
related_data={'attachments': form.cleaned_data['attachments']} )

django registration - allows multiple users for an email id

I am trying out django-registration. I found that it allows multiple registration for same emailid. I want to prevent that. What is the best way to do that?
ok, I see there is a subclass RegistrationFormUniqueEmail. Now, how to use this class? I changed this
def get_form_class(self, request):
return RegistrationFormUniqueEmail
But, it must be better to change this from my application rather than in source code. So, how do I do that?
thanks
Once you've added registration to your settings file, you can use the form in your views.py like so:
from registration.forms import RegistrationFormUniqueEmail
form = RegistrationFormUniqueEmail()
That's it. That will give you the form that you need and will take care of the unique email validation.

Django custom managers - how do I return only objects created by the logged-in user?

I want to overwrite the custom objects model manager to only return objects a specific user created. Admin users should still return all objects using the objects model manager.
Now I have found an approach that could work. They propose to create your own middleware looking like this:
#### myproject/middleware/threadlocals.py
try:
from threading import local
except ImportError:
# Python 2.3 compatibility
from django.utils._threading_local import local
_thread_locals = local()
def get_current_user():
return getattr(_thread_locals, 'user', None)
class ThreadLocals(object):
"""Middleware that gets various objects from the
request object and saves them in thread local storage."""
def process_request(self, request):
_thread_locals.user = getattr(request, 'user', None)
#### end
And in the Custom manager you could call the get_current_user() method to return only objects a specific user created.
class UserContactManager(models.Manager):
def get_query_set(self):
return super(UserContactManager, self).get_query_set().filter(creator=get_current_user())
Is this a good approach to this use-case? Will this work? Or is this like "using a sledgehammer to crack a nut" ? ;-)
Just using:
Contact.objects.filter(created_by= user)
in each view doesn`t look very neat to me.
EDIT Do not use this middleware approach !!!
use the approach stated by Jack M. below
After a while of testing this approach behaved pretty strange and with this approach you mix up a global-state with a current request.
Use the approach presented below. It is really easy and no need to hack around with the middleware.
create a custom manager in your model with a function that expects the current user or any other user as an input.
#in your models.py
class HourRecordManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(created_by=user)
class HourRecord(models.Model):
#Managers
objects = HourRecordManager()
#in vour view you can call the manager like this and get returned only the objects from the currently logged-in user.
hr_set = HourRecord.objects.for_user(request.user)
See also this discussion about the middelware approach.
One way to handle this would be to create a new method instead of redefining get_query_set. Something along the lines of:
class UserContactManager(models.Manager):
def for_user(self, user):
return super(UserContactManager, self).get_query_set().filter(creator=user)
class UserContact(models.Model):
[...]
objects = UserContactManager()
This allows your view to look like this:
contacts = Contact.objects.for_user(request.user)
This should help keep your view simple, and because you would be using Django's built in features, it isn't likely to break in the future.
It seems necessary to use the middleware to store the user information.
However, I'd rather not modify the default ModelManager objects, but hook it upto a different manager, that I will use in the code, say in your case user_objects instead of objects.
Since you will use this only within views that are #login_required you dont need all the complex error handling in the Middleware.
Just my 2ยข.
Or even simpler and use foreign key to retrieve queryset.
If you have model like that
class HourRecord(models.Model):
created_by = ForeignKey(get_user_model(), related_name='hour_records')
You can query HourRecords in a view by user with simply:
request.user.hour_records.all()