AbstractClass ForeignKey reference to AbstractClass - django

I am building a compliance management system where I have the following requirements for most models in my project:
My Requirements
Users with certain roles can change fields, but this has to lead to a new "version" with a draft status
After approval through certain other roles (i.e. managers) this version of the model has to be published
History has to be accessible for all roles
Current Situation
Because available django-apps did not meet all these requirements I created to following constuct:
Every model (e.g. Policy) has one Master and one Detail model.
The Master model has the following fields:
id
deleted
currentActiveDetail (ForeignKey)
The Detail model has the following fields:
id
majorVersion
minorVersion
author
masterModel (ForeignKey to Master)
user (ForeignKey to auth.User)
lifecycleStatus (Choice, i.e. 'Draft', 'Waiting Approval', 'Approved', 'Obsolete')
a lot of content fields (e.g. Description, Text,...)
a lot of methods
Challenges/Questions
Because I have a lot of such use cases I want to make a MasterAbstractClass and a DetailAbstractClass but I can't find a solution to the following challanges:
How to "reserve" the ForeignKey fields in the abstract class (I know that I can't define them because there is no table in the database). I thought if using the contenttype framework but it seems to be inadequate because
it's a reference from one AbstractClass (Master) on one hand to another AbstractClass (Detail) on the other hand
for the user foreign key I know exactly to what model I want to reference
EDIT 1: Just realized that the 3rd challange is no problem at all, the following works:
class ContentLifecycleDetailClassModel (models.Model):
author = models.ForeignKey('auth.User', null=True)
class Test(ContentLifecycleDetailClassModel):
pass
CLI
from polls.models import Test
from django.contrib.auth.models import User
test = Test()
user = User.objects.first()
test.author=user
test.save()
x = Test.objects.first()
print x.author.username # <-- Working
EDIT 2:
This issue was already discussed in 2009 but only a solution for one abstract class is used in one implementation which does not fit these needs: http://djangotricks.blogspot.co.at/2009/02/abstract-models-and-dynamicly-assigned.html
EDIT 3:
I ended up using the ContentType Framework to solve (1) and (2)
masterModel_type = models.ForeignKey(ContentType, blank=True, null=True)
masterModel_id = models.PositiveIntegerField(blank=True, null=True)
master_Model = GenericForeignKey('masterModel_type', 'masterModel_id')

Solved the challenges by steps mentioned in the three edits. It was not the clean solution I expected but it is working..

Related

Django: User vs UserProfile

I'm building a site where we ask users several personal details (birth date, phone number, address, marital status etc. many more).
Option 1: User model only. Put these personal fields in class User(AbstractUser) model
class User(AbstractUser):
birth_date = ...
phone_number = ...
Option 2: User + UserProfile models: separate login-related data (User) from personal data (UserProfile) like:
class User(AbstractUser):
pass
class UserProfile(models.Model):
user = models.OneToOneField(
User,
to_field="id",
primary_key=True,
on_delete=models.CASCADE,
related_name="user_profile",
)
birth_date = ...
phone_number = ...
Which one is the best practice?
This is subjective, based on each ones opinion/needs. But will go over the technical differences:
Option 1 is easier to use in your code and more efficient as you don't have to do JOINs between tables in order to get all information
Option 2 will make the model look cleaner, with only the most important fields present. This is helpful for data engineers or data analyst who work a lot on SQL visualisation tools
Unless UserProfile has 20+ fields, I would personally go with Option 1.
As always the answer will be "it depends". If your users might have different types of profiles/roles that may require additional set of fields I would go with the second option because it would allow you to have one user model that can combine traits and functionalities of several users. Depending on situation you can call specific method of profile (look for Delegation pattern for more examples).

Transform ForeignKey into OneToOne field depending on current user

I have a setup like in the following (simplified) example:
class Pizza(models.Model):
name = models.CharField(max_length=100)
class Favorite(models.Model):
user = models.ForeignKey(User, related_name='favorites')
pizza = models.ForeignKey(Pizza, related_name='favorites')
class Meta:
unique_together = (('user', 'pizza'),)
I know that I can filter the favorites property on a Pizza model instance by the user property, but I'd like to abstract this.
In my application, a user should only have access to its personal Favorite model instances. To ensure this, I find myself having to filter all the time and do a lot of ugly and inefficient stuff.
I want to abstract this so that when a user is logged in, I should be able to access pizza.favorite, instead of pizza.favorites, which is automatically mapped to the current user's Favorite model for this particular Pizza model. Ideally, I should also be able to filter on this property (no possible with the #property annotation). Basically, it should act like the ForeignKey is now a OneToOne field.
Any ideas on how could I achieve this behaviour? I should note that un-authorized users do not concern me. The application does not provide anonymous access, so that edge case can be disregarded.
I am using Django 1.7 and I am also open to using the development version, if that would help.

ManyRelatedManager call returns empty list when it should return at least one result

I have two models designated for tracking what users have upvoted an Article instance (in another app, in this case articlescraper).
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User)
articles_upvoted = models.ManyToManyField('useraccounts.UpvotedArticle',
null=True,
blank=True)
class UpvotedArticle(models.Model):
article = models.ForeignKey('articlescraper.Article')
user = models.ForeignKey(User)
In a Django shell, I've tried to get a list of articles by interacting with UserProfile:
a = UserProfile.objects.get(pk=1)
a.articles_upvoted.all()
Which returns:
[]
However, then I went a little further:
b = UpvotedArticle.objects.filter(user=User.objects.get(pk=1))
b
Which returns:
[<UpvotedArticle: Arch Linux Lexmark S305 Drivers>, <UpvotedArticle: Structure of a Haystack project>]
Which is the expected behavior, and is mirrored in the Django admin in both UserProfile and UpvotedArticle categories.
I don't understand, however, why attempting to get a list of articles can't be done the way I initially tried to using a.articles_upvoted.all() if the two models are linked.
Because these aren't the same relationship. By defining a ForeignKey on one side, and a ManyToMany on the other, you've given the database two separate places to store information about article upvoting.
You should remove the ManyToManyField on UserProfile, and just use the automatic reverse relationship:
a = UserProfile.objects.get(pk=1)
a.upvotedarticle_set.all()
Alternatively, you could recognize UpvotedArticle as the "through" table of the ManyToMany relationship, and mark it as such explicitly in the definition of articles_upvoted - note though that the relationship should be with articlescraper.Article, not UpvotedArticle:
article_upvoted = models.ManyToManyField(articlescraper.Article, null=True,
blank=True, through=UpvotedArticle)
Although since you're not adding any extra data on that relationship, which is the usual reason for defining an explicit through table, you may want to drop it completely and just rely on the automatic one that Django will create.

django guardian, permissions and extending django auth groups to 'organization' models

django guardian https://github.com/lukaszb/django-guardian is a really well written object-level permissions app; and I have actually read up on and used quite a number of other django object level permissions app in various django projects.
In a recent project that I am working on, I decided to use django guardian but I have a model design question relating to the pros and cons of two possible approaches and their respective implications on sql query performance:-
using django.contrib.auth.models.Group and extending that to my custom organization app's models; or
using django.contrib.auth.models.User instead and creating an m2m field for each of the organization type in my organization app.
Approach #1
# Organisation app's models.py
from django.contrib.auth.models import Group
class StudentClass(models.Model):
name = models.CharField('Class Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
size = models.IntegerField('Class Size', blank=True)
class SpecialInterestGroup(models.Model):
name = models.CharField('Interest Group Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
description = models.TextField('What our group does!', blank=True)
class TeachingTeam(models.Model):
name = models.CharField('Teacher Team Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
specialization = models.TextField('Specialty subject matter', blank=True)
In this approach, when a user is added to a group (django group) for the first time, the group object is created and also assigned to one of these 3 classes, if that group object does not yet belong to the class it is added into.
This means that each StudentClass object, sc_A, sc_B etc, can possibly contain a lot of groups.
What that means is that for me to ascertain whether or not a specific user (say myuser) belongs to a particular organization, I have to query for all the groups that the user belong to, via groups_myuser_belongto = myuser.groups and then query for all the groups that are associated to the organization I am interested in, via groups_studentclass = sc_A.groups.all() and since I now have 2 lists that I need to compare, I can do set(groups_myuser_belongto) && set(groups_studentclass), which will return a new set which may contain 1 or more groups that intersect. If there are 1 or more groups, myuser is indeed a member of sc_A.
This model design therefore implies that I have to go through a lot of trouble (and extra queries) just to find out if a user belongs to an organization.
And the reason why I am using m2m to groups is so as to make use of the Group level permissions functionality that django guardian provides for.
Is such a model design practical?
Or am I better off going with a different model design like that...
Approach #2
# Organisation app's models.py
from django.contrib.auth.models import User
class StudentClass(models.Model):
name = models.CharField('Class Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
size = models.IntegerField('Class Size', blank=True)
class SpecialInterestGroup(models.Model):
name = models.CharField('Interest Group Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
description = models.TextField('What our group does!', blank=True)
class TeachingTeam(models.Model):
name = models.CharField('Teacher Team Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
specialization = models.TextField('Specialty subject matter', blank=True)
Obviously, this model design makes it really easy for me to check if a user object belongs to a particular organization or not. All I need to do to find out if user john is part of a TeachingTeam maths_teachers or not is to check:
user = User.objects.get(username='john')
maths_teachers = TeachingTeam.objects.get(name='Maths teachers')
if user in maths_teachers.users.all():
print "Yes, this user is in the Maths teachers organization!"
But what this model design implies is that when I add a user object to a group (recall that I want to use django guardian's Group permissions functionality), I have to make sure that the save() call adds the user object into a "Maths Teachers" group in django.contrib.auth.models.Group AND into my custom TeachingTeam class's "Maths Teachers" object. And that doesn't feel very DRY, not to mention that I have to somehow ensure that the save calls into both the models are done in a single transaction.
Is there a better way to design my models given this use case/requirement - use django groups and yet provide a way to "extend" the django's native group functionality (almost like how we extend django's user model with a "user profile app")?
My take on this (having developed django apps for a long time) is that you should stick with the natural approach (so a StudentClass has Users rather than Groups). Here "natural" means that it correspond to the actual semantics of the involved objects.
If belonging to a specific StudentClass must imply some automatic group (in addition to those granted to the user), add a groups m2m to the StudentClass model, and create a new authentication backend (extending the default one), which provides a custom get_all_permissions(self, user_obj, obj=None) method. It will be hooked by https://github.com/django/django/blob/master/django/contrib/auth/models.py#L201
In this method query for any group associated to any Organization the user belongs to. And you don't need to do 1+N queries, correct use of the ORM will navigate through two *-to-many at once.
The current ModelBackend method in https://github.com/django/django/blob/master/django/contrib/auth/backends.py#L37 queries get_group_permissions(user_obj) and adds them to the perms the user has assigned. You could add similar behavior by adding (cached) get_student_class_permission and other corresponding methods.
(edited for clearer prologue)
Obs: There is another approach which is to use generic relationships, in this approach you would have the User model instance pointing to it's resources through the contenttypes framework. There is a nice question here on SE explaining this approach.
About the performance: There are some clues that a single select with JOIN is cheaper than many simple select (1,2,3). In this case opition 2 would be better.
About the usability: The first approach is hard to explain, hard to understand. IMHO go for no. 2. Or try the generic relations.

Handling uniqueness in a multi-tenant Django setup

I ahve a multi tenant Django database. All my multi tenant enabled models import from a class AccountSpecificModel, which as a FK to a class Account. Now I have some unique=True and unqiue_together in Meta, which do not specify account.
In my scenario, for AccountSpecificModel uniqueness has no menaing unless it takes care of account, which means I want to convert each unique to a unique_together with account, and simlar for unique_together.
How can I do this?
If I'm understanding you correctly, this is what you want:
class Client(models.Model):
account = models.ForeignKey(Account)
email = models.EmailField()
class Meta:
unique_together = (('account', 'email'),)
Notice the "two-tuple" I assigned unique_together to. You can do a standard tuple, but if there is more than one field that you want to be unique together, you'll just have to change it anyway.
Now 500 different accounts can have a client with the email example#example.com, but no account can have 2 clients with the same email.
I did not understand your question fully, but I found that when implementing complicated django model class customizations one good solution is a class factory. I found that it is less complicated and less surprising than multiple inheritance and other magic.
def factory(superclass, arguments):
class SomeClass(superclass):
[...]
class Meta:
[...]
return SomeClass
RealClass = factory(SuperClass, args)
I tried other approaches on a similar sounding problem for a while, but a factory for the class solved the problem in the end. Think about it, it may help you to solve your problem.