Cleanest way to implement multiple email system in django? - django

Hey everyone, I am pretty sure this is a fairly common problem.
So in order to register an account my site you need an email address from certain school domain (like facebook). This wouldn't be that big a problem until you start integrating other apps, like django-notification and django-registration and django-socialregistration into your site where they are sending email via user.email.
I have asked my users and most of them want an 'active_email' option - that means that they can change the email to their designated gmail or whatever.
I have come up with the following solution which isn't the cleanest of all:
First, I inherit from User in django.contrib.auth and call this new class MultipleEmailUser, with email=active_email and official_email=sch_email.
Then I override django.contrib.auth's UserManager to change the API slightly,
And the most painful part is to change all the source code that has User.object.find() to MultipleEmailUser.find().
Can someone suggest me a cleaner way? (My biggest headache arise from other apps only permitting to send email to User.email.)

You don't need - or want - to modify the User class. Just set up a UserProfile class with a OneToOneField back to User, and set the AUTH_PROFILE_MODULE setting. See the documentation.

Rather than a true datastore attribute, you could use a property called 'email' to expose access to whatever data you want.
Basic property syntax (from the python docs):
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
In your case, you could create true datastore attributes named, for instance, auth_email and active_email. Then, the User.email property could perform some logic in its getter function, to determine which one to return (i.e. if active_email is set, return it; otherwise, return auth_email)
It's worth noting that the syntax for property has undergone some flux. As of python 2.7, it can be implemented in a more readable way, as a decorator:
class User(BaseModel):
#property # email
def email(self):
if self.active_email:
return self.active_email
return self.auth_email

Related

Django: Custom default Manager with user based queries for all views/interfaces (custom, admin, graphene, rest, ...)

In Django I'm trying to implement some kind of a "security middleware", which gives access to certain db information only, if the logged in user matches.
Up to now I found two approaches: middleware or custom function in manager (Both explained here Django custom managers - how do I return only objects created by the logged-in user?).
Example for cleanest solution: custom function
class UserContactManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(creator=user)
class MyUser(models.Model):
# Managers
objects = UserContactManager()
# then use it like this in views
data = MyUser.objects.for_user(request.user)
However, this solution only works, if you have control over the code which invokes this custom function (here: for_user()).
But if you are using third parties apps like Django-REST, Graphene or AdminViews, they don't have the possibility to configure a specific query-func to use.
My goal:
Replace the default Manager
Add user-based filters to query_set
Use the model as normal in all app configurations
Idea (pseudo code!)
from django.x import current_user
class UserBasedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(author=current_user.name)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = UserBasedManager() # The default manager.
Now I could use the model Book as normal in all extensions.
I know this solution would have some drawbacks, which are okay for me:
Can be only used when user is available (e.g. no direct script support)
Handling no or anonymous user is missing (left it away to keep example short)
My use case
I have a project, which shall give access to data by different interfaces (admin pages, custom views, rest, graphql).
As I have so many interfaces, I don't want to implement the access-rights in the views. This would cost too much time and is hard to maintain and the risk for security problems in one specific view/interface is too high.
Let's say I'm storing commits of git repositories in a database.
And the user shall get access to all commits, which are part of repos the user has READ access.
The question is: How can I implement this as a generic, view/interface independent solution?
Install django-threadlocals package and call get_current_user function to get the current user
from threadlocals.threadlocals import get_current_user
class UserBasedManager(models.Manager):
def get_queryset(self):
current_user = get_current_user()
return super().get_queryset().filter(author=current_user.name)

M2M relationship validation in Django

I have these two models:
class Test(models.Model):
problems = models.ManyToManyField('Problem')
...
class Problem(models.Model):
type = models.CharField(max_length=3, choices=SOME_CHOICES)
...
Now, while adding Problems to a Test, I need to limit the number of particular type of problems in the Test. E.g. a Test can contain only 3 Problems of type A, and so on.
The only way to validate this seems to be by using m2m_changed signal on Test.problems.through table. However, to do the validation, I need to access the current Problem being added AND the existing Problems - which doesn't seem to be possible somehow.
What is the correct way to do something like this? M2M validation seems to be a topic untouched in the docs. What am I missing?
You are right on the part that you have to register an m2m_changed signal function like the following:
def my_callback(sender, instance, action, reverse, model, pk_set, **kwargs)
If you read the documentation you 'll see that sender is the object-model that triggers the change and model is the object-model that will change. pk_set will give you the pkeys that will be the new reference for your model. So in your Test model you have to do something like this:
#receiver(m2m_changed)
def my_callback(sender, instance, action, reverse, model, pk_set, **kwargs):
if action == "pre_add":
problem_types = [x.type for x in model.objects.filter(id__in=pk_set)]
if problem_types.count("A") > some_number:
raise SomeException
Mind though that an Exception at that level will not be caught if you're entering fields from Django admin site. To be able to provide user friendly errors for django admin data entry, you'll have to register your own form as admin form. In your case, you need to do the following:
class ProblemTypeValidatorForm(ModelForm):
def clean(self):
super(ProblemTypeValidatorForm, self).clean()
problem_types = [x.type for x in self.cleaned_data.get("problems") if x]
if problem_types.count("A") > some_number:
raise ValidationError("Cannot have more than {0} problems of type {1}"
.format(len(problem_types), "A")
then in your admin.py
#admin.register(Test)
class TestAdmin(admin.ModelAdmin):
form = ProblemTypeValidatorForm
Now keep in mind that these are two different level implementations. None will protect you from someone doing manually this:
one_test_object.problems.add(*Problem.objects.all())
one_test_object.save()
Personal opinion:
So keeping in mind the above, I suggest you go with the ModelForm & ModelAdmin approach and if you're providing an API for CRUD operations, make your validations there as well. Nothing can protect you from someone entering stuff in your db through django shell. If you want such solution types you should go directly to your db and write some kind of magic trigger script. But keep in mind that your db is actually data. Your backend is the one with the business logic. So you shouldn't really try to impose business rules down to the db level. Keep the rules in your backend by validating your data at the spots where create/update happens.
You can't override save for a M2M I'm afraid, but you can achieve what you want.
Use the m2m_changed signal where the action is pre_add.
The 'instance' kwarg will be the Test model the problem is being added to.
The 'pk_id' kwarg will be the primary key of the Problems being added (1 or more).
The validation logic will be something like this:
p_type = Problem.objects.get(id=kwargs['pk_id']).type
type_count = kwargs['instance'].problems.filter(type=p_type).count()
if p_type == 'A' and type_count == 3:
raise Exception("cannot have more than 3 Problems of type A")
[sorry don't have django on hand to verify the query]

Setting the authenticated user on a Django model

I have a number of models that need to refer back to the user that created/updated them. Generally this just involves passing request.user to the relevant attribute, however I'd like to make this automatic if possible.
There's an extension for Doctrine (a PHP ORM) called Blameable that will set a reference to the currently authenticated user when persisting a model instance, e.g.:
class Post
{
/**
* Will set this to the authenticated User on the first persist($model)
* #ORM\ManyToOne(targetEntity="User", inversedBy="posts")
* #Gedmo\Blameable(on="create")
*/
private $createdBy;
/**
* Sets this to the authenticated User on the first and subsequent persists
* #ORM\ManyToOne(targetEntity="User")
* #Gedmo\Blameable(on="update")
*/
private $updatedBy;
}
To get the same functionality in Django, my first thought was to try and use pre_save signal hooks to emulate this - however I'd need to access the request outside of a view function (looks possible with some middleware but a bit hacky).
Is there something similar already available for Django? Am I better off explicitly passing the authenticated user?
The level of decoupling Django has makes it impossible to automatically set the user in a model instance.
The middleware solution is the way to go. When I need to do this, I just add to the save() method, like so:
class MyObject(models.Model):
def save(self, *args, **kwargs):
if not self.created_by:
self.created_by = get_requests().user
super(MyObject, self).save(*args, **kwargs)
as for the "hackyness" of storing the requests in a global dictionary, I think you'll get over it. Someone once said of this pattern, "It's the worst one, except for all the others".
P.S. You'll also find it really useful if you want to use django.contrib.messages from deep within your code.

For all my foreignKeys, should i use User or UserProfile in Django?

Here is the sutuation i hit.
I have both User and ProfileUser. I would like to add additional logic to the model and since I can't add it to the Django User model, I have to add it to the ProfileUser. Currently all my models however have ForeignKey(User). Should I keep them like that or should I user ForeignKey(UserProfile) on my other models?
Example for my view if I keep the ForeignKey(User):
class myview(request):
user = request.user
userProfile = user.get_profile()
neededStuff = userProfile.get_needed_stuff()
and then in the UserProfile model:
def get_needed_stuff(self):
user= self.user # Or actually, is this right
goals = Goal.objects.get(<conditions that i wont bother writing here>)
return goals
So for this case, and for further development of the site, which foreign key should i use?
I think You should use User. UserProfile should be custom and can differ on each project. So if you will use same code for another project you can probably fail because of that. Also it is always easy to get user object in code and from that you have no problems to get profile user.get_profile() as you show (and profile is not always needed). So ingeneral I think it will be easier to use other modules and passing them just user object (or id) and not the profile.
What is also could be the solution - write your own class which will be responsible for the users. Just write methods to return profile, return stuff_needed or whatever you want and everything just by passing user object and additional parameters about what you want.
So in short, I'm for using User for Foreign keys, because in my opinion it just more logical, while the User model is always the main one (you always have it) and UserProfile is just extension.
Ignas
If you just want all the goals belonging to a specific user add a foreign key to User in your Goal model.
class Goal(models.Model):
user = models.ForeignKey(User)
def myview(request):
goals = Goal.objects.filter(user=request.user)
Or alternately save all the goals for a user on your UserProfile model and do
def myview(request):
user_profile = user.get_profile()
goals = user_profile.goals
...or use a method to do processing to calculate them
goals = user_profile.calculate_goals()
I've been pondering the same thing myself for one of my sites but i decided to use UserProfile rather than User.
Not sure if its the right decision but it just seems more flexible.

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