Making a django pluggable app generic, without tying it to a model - django

How would you go about making a pluggable django app, that can accept data from any model and then perfom some action with that data (i.e Save to DB, Send email). This functionality should be generic and should not be tied to a specific model.

It depends on what functionality your app would provide and in what way you'd expect users of your app to use it's api. For interacting with other models you don't know about there are a few ways, depending on what your reusable app does. You can make forms, views etc that would accept a model class or instance as a property or parameter. The other way would be for the users of your app to specify their relevant models in settings.py much like auth deals with user profiles. For example if your app needs to know about a model class that provides info about gadgets the user would specify:
#user's settings.py
GADGETS_MODEL='myapp.CustomSuperFunGadgets'
To get the class for the user specified model you would do:
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
if not getattr(settings, 'GADGETS_MODEL', False):
raise ImproperlyConfigured('You need to set GADGETS_MODEL'
'in your project settings')
try:
app_label, model_name = settings.GADGETS_MODEL.split('.')
except ValueError:
raise ImproperlyConfigured('app_label and model_name should'
' be separated by a dot in the GADGETS_MODEL set'
'ting')
try:
model = models.get_model(app_label, model_name)
if model is None:
raise ImproperlyConfigured('Unable to load the gadgets '
'model, check GADGETS_MODEL in your project sett'
'ings')
except (ImportError):
raise ImproperlyConfigured('Unable to load the gadgets model')
#at this poing model will hold a reference to the specified model class
Another way of interacting with models you don't know about is your app to provide custom model fields or managers or special properties that would just attach signal handlers to the model they are attached to.
As I said it all depends on what problem your reusable app is trying to solve, and the approach you should take is always based on that.

Related

Forcing unique email address during registration with Django

Is there a simple way to force unique email address's during registration with website built with Django?
I've seen some "addons?" like HMAC, but it seems a bit too complicated for what I am trying to achieve.
Also, would it be possible to accept registration only from a list of domains? (such as only emails from "#google.com")
I had the same problem and solved it by extending the AbstractUser class to my own class MyUser and changing the defaults.
Then by making the this class MyUser as a default model class for all my users I could apply this property(unique E-Mail) to all my users on my web app.
Create an app myuser. There in models.py:
from django.contrib.auth.models import AbstractUser
#create your own user class.
class MyUser(AbstractUser):
def __init__(self, *args, **kwargs):
self._meta.get_field('email').blank = False
self._meta.get_field('email')._unique = True
super(MyUser, self).__init__(*args, **kwargs)
#Changed the defaults above.
#Give any additional field you want to associate your user with.
NOTE: AbstractUser already has all the basic fields you would want a User Model to have. For example: username, password, email etc. Check all of them here.
The last thing you would want to do is add the following in your setting.py
AUTH_USER_MODEL = 'myuser.MyUser'
This will make sure that the default user is associated with your web app is the extended(modified) MyUser class. This will provide you with all the basic functionalities that django provides for a User.
login
logout
in your views: you can get user instance in: request.user
etc.
I would like to suggest that you may need some additional code(in forms.py and views.py) to create a user through this type of class. I hope you will manage that. This should be enough to guide you in the right direction.
Maybe a library would have helped but since you needed an authentication for emails' domains as well, I think this should do the trick. In my humble opinion, you can't always depend on the 3rd party libraries for every other functionality.
Lastly, as you asked to authenticate a user coming only from a domain like #gmail.com or #outlook.com, a simple check in your django forms' clean method would do the trick. I hope you know how to handle django forms. If not, then you can learn about them in the official docs. They are an essential part of Django.
You can check the E-Mail with this logic:
email = self.cleaned_data['email']
email_source = email.split('#')[-1]
#email_source will now have values like: gmail.com, outlook.com etc
#you can now validate email_source now like:
permitted_sources = ['gmail.com' , 'outlook.com' , ]
if email_source in permitted_sources:
return cleaned_data
else:
raise forms.ValidationError('Error Message')
#Note: This logic should be kept in your clean method.
I hope this guides you. Thanks.

Add Object Level Permissions to Admin Interface of other APP (e.g. auth)

I use django-guardian for object level permissions. The documentation how to integrate this into own code is good:
http://packages.python.org/django-guardian/userguide/admin-integration.html
But how can I add this to models of other apps? I don't want to modify the code of e.g. django.contrib.auth.
I found a solution in django-reversion's source code. There is a helper called patch_admin(). Here is the snippet modified for django-guardian.
# Copy of django-reversion helpers.py
def patch_admin(model, admin_site=None):
"""
Enables version control with full admin integration for a model that has
already been registered with the django admin site.
This is excellent for adding version control to existing Django contrib
applications.
"""
admin_site = admin_site or admin.site
try:
ModelAdmin = admin_site._registry[model].__class__
except KeyError:
raise NotRegistered, "The model %r has not been registered with the admin site." % model
# Unregister existing admin class.
admin_site.unregister(model)
# Register patched admin class.
class PatchedModelAdmin(GuardedModelAdmin, VersionAdmin, ModelAdmin): # Remove VersionAdmin, if you don't use reversion.
pass
admin_site.register(model, PatchedModelAdmin)
from django.contrib.auth.models import Group
patch_admin(Group)

Anyone think django's user model is too tightly coupled with auth?

I'm trying to learn Django and I would like feedback from anyone who has any MVC/MTV/PHP/Ruby framework experience. Does anyone find that the user model is too tightly coupled with auth?
Background: When you first implement authentication for Django, you include the module django.contrib.auth
This will bring in several models like User, Group, Message etc. Let's focus on the User model as this is the one of the most important tables in any website.
In short the User table has these fields
User
username max_length 30, unique, [letters, digits, underscores]
password max_length 75
email max_length 75
...and about 8 other useful fields like first_name, last_name, etc.
Goal:
I want to remove username and use email as the login for every user. It's a pretty simple request that many websites use these days.
I don't want to monkey patch the core code since this will make upgrading more difficult later on. This means modifying the User model is out of the question. I only want to do a few simple and basic things I expect a few frameworks to do so let me address how Django does it.
Adding new fields to the User model
Django docs says to use create another table and insert the fields there. You will have a one to one relationship between the User table and the Profile table.
eg.
If You want to add an image field to each user you add it to the profile table. A join query is made every single time. They've even specified a constant to tell the framework what table to use:
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
I don't think it's the best practice to have to do a join query every time I want a field that should belong to the user table.
Another option is to use the function add_to_class.
The django community has stated it's not good to define new fields outside of the main class because other developers who add methods won't know all the data members.
Editing old fields
The auth module does a check against two fields username and the hashed password. Looking at the above table I would need to change the username model to accept these properties. Length of 75 with all the valid characters of the email. The django suggests I check against the email field.
Two problems arise if I use the email field to auth against:
I need to write a new class to be used in a constant AUTHENTICATION_BACKEND, so it checks against the email field and I have an unused field called username.
Adding new methods
In MVC/MTV a design principle is to use fat models skinny controllers. Since the model is declared in auth, I'm not sure how one is supposed to add methods that act on the user model's fields. Since django suggests using a Profile model, I suppose they will have to go there.
Extending the User class
A small annoyance would be that I can't use the name 'User' and instead must use 'Users' or 'Accounts'. A bigger one is I don't think the auth would recognize this new module. Meaning I would have to rewrite a bunch functionality that is is present. This one doesn't bother me as it's something I expect to do in other frameworks.
Any comments are appreciated. I wouldn't ask all these questions and look for solutions if I wasn't truly interested in using django.
I agree that django's incessant clinginess to the auth models is absurd. My job requires me to create ultra scalable and very high load sites which sometimes require user authentication and djano's auth model + permissions does not fit with that.
Fortunately, it's not difficult to replace.
First, create a custom User model.
class User(models.Model):
...fields...
#Define some interface methods to be compatible.
def get_and_delete_messages(self):
def is_active(self):
def is_anonymous(self):
def is_authenticated(self):
def is_staff(self):
def has_perm(self, perm_list):
Second, create your own authentication back-end.
class LocalAccount(object):
"""
This checks our local user DB for authentication
"""
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(alias=username)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.select_related().get(pk=user_id)
except User.DoesNotExist:
return None
#settings.py
AUTHENTICATION_BACKENDS = (
'helpers.auth.LocalAccount',
)
That should solve most of your issues, I don't even think all of the methods you would find on django.contrib.auth.User are necessary, I recommend trying it out.
The one gotcha here is that the admin may start to bitch, fortunately that's really easy to patch using simple python inheritance as well. That's another question though :)
At the end of the day your project's auth backend needs some sort of store for auth credentials. That the default auth backend is tightly coupled to the User model is not strange in this respect. It's easy enough to substitute your own definition for the user model if you write your own auth backend, as I have in the past.
I created my Profile model and use AUTH_PROFILE_MODULE, so I have complete control over my model, I can modify fields, add methods, etc. Now I'm thinking about using cache and writing middleware that will get profile from cache if possible.
To login using email you could write very simple auth backend:
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
class EmailModelBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
except User.DoesNotExist:
return None

Best way to add convenience methods to a Django Auth User model?

I want to add a convenience/model method to the django.contrib.auth.models.User model. What is the best practice for doing this since, last time I checked, extending the User model was considered bad practice.
I have a separate custom UserProfile model. Should I be using that for all User-related convenience methods?
It depends what you are trying to add to the model. If you want to add more information about the user, then it is generally recommended that you use the UserProfile method: http://docs.djangoproject.com/en/dev/topics/auth/#storing-additional-information-about-users
However, if you just want to add custom methods or managers to the User model, I would say that it's more logical to use a proxy model, like so:
from django.contrib.auth.models import User
class UserMethods(User):
def custom_method(self):
pass
class Meta:
proxy=True
A proxy model will operate on the same database table as the original model, so is ideal for creating custom methods without physically extending the model. Just replace any references to User in your views to UserMethods. (And of course you can use this in the admin tool by unregistering the User model and registering your proxy model in its stead.)
Any instances of the original User model that are created will be instantly accessible via the UserMethods model, and vice-versa. More here: http://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models
(NB. Proxy models require Django 1.1 and above)
if you want to add custom methods to the User model, I would recommend monkey_patching:
create a file monkey_patching.py in any of your apps::
#app/monkey_patching.py
from django.contrib.auth.models import User
def get_user_name(self):
if self.first_name or self.last_name:
return self.first_name + " " + self.last_name
return self.username
User.add_to_class("get_user_name",get_user_name)
and import it in app's __init__.py file. ie::
#app/__init__.py
import monkey_patching
Yes. No need to mess with the foundations when your user model has a .get_profile() function attached to it.
2013 update:
in 1.5 you can sustitute a custom User model and add whatever you want https://docs.djangoproject.com/en/dev/topics/auth/customizing/#auth-custom-user
I prefer to use the same UserProfile across various projects I develop and extend User for any project-specific needs. So, common functionality goes to UserProfile, and project-specific functionality goes to custom User. I have not had any adverse effects of having a subclassed User model yet, I wonder if there still exist any with Django 1.0+.

Django, BigIntegerField, and django.contrib.auth.user.id

Django now provides a BigIntegerField for use in django models (available in svn trunk and also 1.2 alpha-1 release).
I need my django.contrib.auth.user model to have a BigIntegerField as its auto-incrementing primary key, whereas it currently uses an auto-incrementing IntegerField as its primary key. Also, wherever contrib.auth.user is used as a ForeginKey, it would need to be BigIntegerField as well.
What is the best and safest way to go about achieving this?
While I'm not sure why you need a BigIntegerField on User (you must have a whole lotta users) its pretty easy to implement. First you'll need to get a database migration system like South. We'll use this to do a handful of migrations of your current data. If you don't have anything in your database then just ignore this part and skip to the end.
I would start by making a custom user class which inherits from the contrib.auth version like so:
from django.contrib.auth.models import User, UserManager
from django.db import models
class BigUser(User):
id = models.BigIntegerField(pk = True)
objects = UserManager()
#this lets you transperantly use any
#query methods that you could on User
Then use South's data-migration capability to make a copy of all of you User.objects.all() into your new BigUser model.
Then go through and ADD a foriegnkey in each model where its needed. DO NOT delete the original FK yet, otherwise you're links will be lost. After adding the new keys do another schema migration.
Then make another data migration which copies the FK's from the old User model to the new BigUser model. Migrate that data.
Then its safe to delete the old FK to the User model.
If you want to avoid changing the rest of your code to use the new field-name for the BigUser you can use the South rename-field utility (South can't auto-detect field renames so make sure to read the docs).
If you don't have any data in the database then you can simply implement the class above and drop it into your current models.
If you need help writing data-migrations you'll have to post a model or two.
Since you need something that's a "drop-in" replacement for User you'll need two more steps:
First we need to create a custom authentication back-end, this makes sure that any authentication requests go to your new model and that request.user returns BigUser and not User. Just cut and paste this snippet into a file called auth_backend.py in the same directory as settings.py:
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_model
class CustomUserModelBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = self.user_class.objects.get(username=username)
if user.check_password(password):
return user
except self.user_class.DoesNotExist:
return None
def get_user(self, user_id):
try:
return self.user_class.objects.get(pk=user_id)
except self.user_class.DoesNotExist:
return None
#property
def user_class(self):
if not hasattr(self, '_user_class'):
self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
if not self._user_class:
raise ImproperlyConfigured('Could not get custom user model')
return self._user_class
Then in your settings.py file you need to add this back-end and set the custom user model setting ... like so:
AUTHENTICATION_BACKENDS = (
'auth_backends.CustomUserModelBackend',
)
...
CUSTOM_USER_MODEL = 'your-app-name.BigUser'
This last section of code comes from another website describing subclassing the User model.
Now all you need to do to "drop-in" in the rest of your code is to replace all of the from django.contrib.auth.models import User with from your-app-name import BigUser as User. By doing this you wont have to update any references of User with BigUser
I am weighing the option of changing the code of django.contrib.auth.models.user to include an id field as BigIntegerField primary key.
Seems to me to be the best way to go.
(I am ready to migrate the data manually via sql)