Up until recently, a project I'm working used one mega UserProfile to handle all profile data for two different types of users. Naturally this was messy, and it was about time to refactor it.
In my attempt to refactor the model, I split the model into Requester and Funder and created an abstract UserProfile model which both subclass:
class UserProfile(models.Model):
class Meta:
abstract = True
user = models.OneToOneField(User)
def __unicode__(self):
return unicode(self.user)
class Requester(UserProfile):
def requested(self, event):
"""Check if a user requested an event."""
return self == event.requester
class Funder(UserProfile):
osa_email = models.EmailField(null=True) # The e-mail of the contact in OSA
mission_statement = models.TextField(max_length=256)
And in my settings.py file, I adjusted the AUTH_PROFILE_MODULE.
AUTH_PROFILE_MODULE = "app.UserProfile"
The problem is, when hitting a page that uses "User.get_profile()" it breaks, reporting:
Unable to load the profile model, check AUTH_PROFILE_MODULE in your project settings
I'm not quite sure what's going on here. According to the docs, everything looks right.
Can some explain why this fails? (There are a bunch of alternative solutions I've come across, but I'd much prefer to fix this if possible than adopt some hack.)
What you are trying to do it not possible. AUTH_PROFILE_MODULE is expecting a concrete model, not an abstract one. Concrete means it has a table and can create instances. An abstract model can only be subclassed.
A logic reason why this not possible it that django has no one of knowing which model instance to return for your user. A Requester? A Funder? Simply being an abstract reference gives django no hints. One approach might be to look into the contenttypes framework and maybe come up with a generic UserProfile model containing a reference to the proper sub-profile type. You could then remove the abstract=True from your UserProfile, and create a generic relation to the specific Profile model. AUTH_PROFILE_MODULE would then simply reference that single UserProfile, but its instances can then use the .content_object to get the specific subobject.
There are many ways I'm sure you could address this problem, but I am just commenting on the reason why this specific approach does not work.
Related
I would like to allow the creation of a comment only to those models that are sub-classing a specific mixin.
For example, a Post model will have a reverse GenericRelation relation to a Comments model. The comments model is using a custom content types mechanism implemented on top of django's due to the fact that the project uses sharding between multiple databases. The reverse relationship from Post model to Comments is needed to be able to delete Comments when a Post is also deleted.
Putting a simple coding example of what I would like to achieve:
class Post(models.Model, HasCommentsMixin):
some_fields = ....
class HasCommentsMixin(models.Model):
has_comments = GenericRelation('comments.Comment')
class Meta:
abstract = True
What I would like would me a way to say inside a permission check of a Model: if class is subclass of HasCommentsMixin, allow the creation of a comment. So for the Post model, comments can be created. But if it's not a subclass the Mixin, comments should not be allowed.
I hope I have provided a description that makes sense. I cannot share real code due to product license and protection.
Thank you.
To achieve this, you can use the isinstance() function in combination with the issubclass() function in the permission check to check if the model is a subclass of the HasCommentsMixin.
class IsCommentAllowed(permissions.BasePermission):
def has_permission(self, request, view):
model = view.get_queryset().model
if isinstance(model, HasCommentsMixin) or issubclass(model, HasCommentsMixin):
return True
return False
I have a django 1.6 app with the following (trimmed for clarity)
classes defined. User is the standard django.contrib.auth User class.
class Event(models.Model):
user = models.ForeignKey(User, related_name='events')
name = models.CharField(max_length=64)
class Profile(models.Model):
user = models.ForeignKey(User, related_name='aprofile')
class MemberProfile(Profile):
pass
Here are my admin classes:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', )
class MemberProfileAdmin(ModelAdmin):
model = MemberProfile
fields = ('user', )
readonly_fields = ('user', )
What I'd like to do is display a read-only list of all events for a given member, or at least profile. Of course joining across the User foreign key seems like the way to go, but I am drawing a blank as to how to accomplish this. Here's a summary of attempts so far.
Define an inline admin on the Event class directly referencing the user field, and add it to the ProfileAdmin:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user' # Fails - fk_name 'user' is not a ForeignKey to <class 'solo365.solo_profile.models.profile.Profile'>
...well, no, it sure isn't. But our User has an 'aprofile' field, so...
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile' # Fails - EventInlineAdmin.fk_name' refers to field 'user__aprofile' that is missing from model 'admin_fk_test.Event'.
Ok, those fields look like they should sync up, but perhaps we need to be a little more aggressive:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile__pk' # Fails - 'EventInlineAdmin.fk_name' refers to field 'user__aprofile__pk' that is missing from model 'admin_fk_test.Event'.
I've also tried messing with formfield_for_foreignkey() and friends in both the inline and the regular model admins, but without that fk_name having a valid value, those methods never get called.
I then considered trying to access the events field directly from a Profile's user:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', 'user__events') # Fails - Unknown field(s) (user__events) specified for Profile. Check fields/fieldsets/exclude attributes of class ProfileAdmin.
What about with a custom formfield_for_foreignkey() method? Sadly that never gets called for anything other than the 'user' field. I've also considered a custom get_formsets() method, but frankly I'm not sure how I could use that without a working EventInlineAdmin.
I could of course define a custom field that simply concatenates all of the events and returns that as a string, but ideally I would prefer something like a fully-featured inline (even read-only) than just a chunk o' text. IOW such a custom field would have a method that (ideally) would return an inline form without requiring any sort of custom template, setting of allow_tags, etc.
Am I doomed to have to create a completely custom Form for the Profile admin class? Or is there a simple way to accomplish what I'm trying to do, that I'm just missing?
Update:
Bonus points if a provided solution works for the MemberProfileAdmin class, not just the ProfileAdmin class.
The relation between User and Profile should be a 1:1 relation which would allow the referencing via user__aprofile. Otherwise, the reverse relation of a foreing key is a queryset because one foreign key can be assigned to multiple instances. This is might be the reason why your code failed.
Change it to:
class Profile(models.Model):
user = models.OneToOneKey(User, related_name='aprofile')
This is a bit like using ForeignKey(unique=True).
To know the attributes, it might help to call dir(model_instance) on the model instance in question, or try around in the Django shell (./manage.py shell).
Also, I've experienced that it might be more confusing to assign a custom related_name like in your case where you would expect one profile by looking at the related name but you would actually get back a queryset.
The generated name in that case would be profile_set, and you would have to call profile_set.all() or profile_set.values() to get some actual profiles.
Here is my models.py:
class Item(models.Model):
# ... some irrelevent fields ...
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_lenght=30)
category_id = models.IntegerField()
Tag is actually a general-purpose name. Each item has many different type of tags - currently there are four types: team tags, subject tags, admin tags and special tags. eventually there will probably be a few more.
The idea is, they all have basically the same fields, so instead of having like 4 tables with manytomany relationship, and instead of adding a new column for Item whenever adding a new type, everything is called a 'tag' and it's very easy to add new types without any change to the schema.
Now to handle this in the admin.py I'm using dyamically created proxy models (based on this), as such:
def create_modeladmin(modeladmin, model, name = None):
class Meta:
proxy = True
app_label = model._meta.app_label
attrs = {'__module__': '', 'Meta': Meta}
newmodel = type(name, (model,), attrs)
admin.site.register(newmodel, modeladmin)
return modeladmin
class TagAdmin(models.Model):
def queryset(self):
return self.model.objects.filter(category_id = self.cid)
class TeamAdmin(TagAdmin):
cid = 1
class SubjectAdmin(TagAdmin):
cid = 2
# ... and so on ...
create_modeladmin(TeamAdmin, name='Teams', model=Tag)
create_modeladmin(SubjectAdmin, name='Subject', model=Tag)
#... and so on ...
This works great for me. However, different staff members need different editing permissions - one guy shouldn't access admin tags, while the other should only have access to edit subject-tags and team-tags. But as far as the admin site is concerned - the dynamic models do not exist in the permission list, and I can't give anyone permissions regarding them.
i.e. a user given all permissions on the list will still not have access to edit any of the dynamic models, and the only way to let anyone access them at all is to give him a superuser which obviously defies the point
I searched SO and the web and I can't anyone with a similar problem, and the docs don't say anything about this not in the dynamic models section or the proxy models section. so I'm guessing this is a different kind of problem. Any help would be greatly appreciated
UPDATE
So after some research into it, the answer was simple enough. Since permissions in django are objects that are saved to the database, what I needed to do was simple - add the relevent permissions (and create new ContentType objects as well) to the db, and then I could give people specific pemissions.
However, this raised a new question - is it a good convention to put the function that creates the permissions inside create_modeladmin as a find_or_create sort of function (that basically runs every time) or should it be used as an external script that I should run once every time I add a new dynamic model (sort of like how syncdb does it)?
And is there a way to also create the permissions dynamically (which seems to me like the ideal and most fitting solution)?
of course you can create permissions, django have django.contrib.auth.management.create_permissions to do this
So I've got a UserProfile in Django that has certain fields that are required by the entire project - birthday, residence, etc. - and it also contains a lot of information that doesn't actually have any importance as far as logic goes - hometown, about me, etc. I'm trying to make my project a bit more flexible and applicable to more situations than my own, and I'd like to make it so that administrators of a project instance can add any fields they like to a UserProfile without having to directly modify the model. That is, I'd like an administrator of a new instance to be able to create new attributes of a user on the fly based on their specific needs. Due to the nature of the ORM, is this possible?
Well a simple solution is to create a new model called UserAttribute that has a key and a value, and link it to the UserProfile. Then you can use it as an inline in the django-admin. This would allow you to add as many new attributes to a UserProfile as you like, all through the admin:
models.py
class UserAttribute(models.Model):
key = models.CharField(max_length=100, help_text="i.e. Age, Name etc")
value = models.TextField(max_length=1000)
profile = models.ForeignKey(UserProfile)
admin.py
class UserAttributeInline(admin.StackedInline):
model = UserAttribute
class UserProfile(admin.ModelAdmin):
inlines = [UserAttibuteInline,]
This would allow an administrator to add a long list of attributes. The limitations are that you cant's do any validation on the input(outside of making sure that it's valid text), you are also limited to attributes that can be described in plain english (i.e. you won't be able to perform much login on them) and you won't really be able to compare attributes between UserProfiles (without a lot of Database hits anyway)
You can store additional data in serialized state. This can save you some DB hits and simplify your database structure a bit. May be the best option if you plan to use the data just for display purposes.
Example implementation (not tested)::
import yaml
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField('auth.User', related_name='profile')
_additional_info = models.TextField(default="", blank=True)
#property
def additional_info(self):
return yaml.load(self._additional_info)
#additional_info.setter
def additional_info(self, user_info_dict):
self._additional_info = yaml.dump(user_info_dict)
When you assign to profile.additional_info, say, a dictionary, it gets serialized and stored in _additional_info instead (don't forget to save the instance later). And then, when you access additional_info, you get that python dictionary.
I guess, you can also write a custom field to deal with this.
UPDATE (based on your comment):
So it appears that the actual problem here is how to automatically create and validate forms for user profiles. (It remains regardless on whether you go with serialized options or complex data structure.)
And since you can create dynamic forms without much trouble[1], then the main question is how to validate them.
Thinking about it... Administrator will have to specify validators (or field type) for each custom field anyway, right? So you'll need some kind of a configuration option—say,
CUSTOM_PROFILE_FIELDS = (
{
'name': 'user_ip',
'validators': ['django.core.validators.validate_ipv4_address'],
},
)
And then, when you're initializing the form, you define fields with their validators according to this setting.
[1] See also this post by Jacob Kaplan-Moss on dynamic form generation. It doesn't deal with validation, though.
I'm finding conflicting information on whether to use OneToOneField(User) or ForeignKey(User, unique=True) when creating a UserProfile model by extending the Django User model.
Is it better to use this?:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
or this?:
class UserProfile(models.Model):
user = models.OneToOneField(User)
The Django Doc specifies OneToOneField, while the Django Book example uses ForeignKey.
James Bennett also has two Blog posts that providing conflicting examples as well:
Extending the User Model
User Registration
In the former post, Bennett provides some reasons why he switched to using ForeignKey instead of OneToOneField, but I don't quite get it, especially when I see other posts that recommend the opposite.
I'm curious to know your preference and why. Or, does it even matter?
The only real reason given in the article is that it can be set up so that the admin page for User will show both the fields in User and UserProfile. This can be replicated with a OneToOneField with a little elbow grease, so unless you're addicted to showing it in the admin page with no work at the cost of a bit of clarity ("We can create multiple profiles per user?! Oh no, wait, it's set unique.") I'd use OneToOneField.
Besides the admin page inlines, other reason for the ForeignKey solution is that it allows you to use the correct, default DB manager when objects are accessed with a reverse relation. Consider example from this subclasses manager snippet. Let's say that the Post class definition from the example looks like this:
class Post(ParentModel):
title = models.CharField(max_length=50)
onetoone = models.ForeignKey(SomeModel, unique=True)
children = ChildManager()
objects = models.Manager()
By calling somemodel_instance.post_set.all()[0], you get the desired subclasses objects of the Post class as indicated by defining the first (default) manager as a ChildManager. On the other hand, with OneToOneField, by calling somemodel_instance.post you get the Post class instance. You can always call somemodel_instance.post.subclass_object and get the same result, but the default manager could do any other sort of tricks and the FK solutions hides them nicely.
If you own and can modify the custom manager code you can use the use_for_related_fields attribute instead of using FK in place of legitimate 1to1 field, but even that can fail because of some not-known to me nuisances of the automatic managers. As far as I remember it will fail in the above example.
Other reason to generally not use the OneToOneField related to reverse relations: when you use reverse relations defined via OneToOneField you get an model instance, contrary to Manager for ForeignKey reverse relation and as a consequence there's always a DB hit. This is costly if you do some generic stuff on reverse relations (via _meta.get_all_related_objects()) and do not know and care if you will use them all or not.