Permit creation only if model is subclass of mixin - django

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

Related

What is the difference between creating a model instance using the default Manager and Customer Manager

While going through the official Django documentation I came across the Model Instance reference section in which it is mentioned that you can create an instance of the model using the custom Model using self.create. I wanted to know what's the difference between using the create method and the custom create_method when both are using the same fields and in both the cases the data is being saved in the DB.
Documentation:
https://docs.djangoproject.com/en/2.2/ref/models/instances/#creating-objects
class BookManager(models.Manager):
def create_book(self, title):
book = self.create(title=title)
return book
class Book(models.Model):
title = models.CharField(max_length=100)
objects = BookManager()
book = Book.objects.create_book("Pride and Prejudice")
Difference between these two
book2 = Book.objects.create(title="Pride and Prejudice")
Well in this simplest case, there is no difference. The reason of describing this technique in docs is obvious there
You may be tempted to customize the model by overriding the __init__
method. If you do so, however, take care not to change the calling
signature as any change may prevent the model instance from being
saved. Rather than overriding __init__, try using one of these
approaches:
It means you may be want to set some extra/default values to model instance. If you override constructor for this purpose, it is a little unsafe (and not usually a practice in django). That's why two other techniques for doing this are described. You are mentioning one of them. You can do some extra stuff in custom manager method if you want
class BookManager(models.Manager):
def create_book(self, title):
# you can do some extra stuff here for instance creation
book = self.create(title=title)
# or here when it is saved to db
return book
Otherwise there is no difference.

Django - Meta.base_manager_name - make related argument in the custom queryset and manager

I have a custom model manager and a custom queryset defined specifically for related obj which means I have defined Meta.base_manager_name in the model.
I would like to use a all() manager method which fetches related obj on a OneToOneFeild.
Now I know this does not make sense since OneToOneFeild will always return one obj there is no need for a all() method. I am working on django-oscar project and am extending its "Partner" model. It originally has a field "users" with ManyToManyField and now changed to a OneToOneFeild.
The users field is called in code several times using relation user.partners.all(). I don't want to extend/modify all these places (am I being lazy here?) since I want to keep the code as upgrade friendly as possible and so instead I wanted to have all() model manager defined which will work. Not sure if it is a good idea?
the all() method takes user arg to return queryset of the user instance
class PartnerQuerySet(models.QuerySet):
def all(self, user):
return self.filter(user=user)
class PartnerManager(models.Manager):
def get_queryset(self):
return PartnerQuerySet(self.model, using=self._db)
def all(self, user):
return self.get_queryset().all(users)
class Partner(models.Model):
objects = PartnerManager()
class Meta:
base_manager_name = 'objects'
The problem is when it is used with related obj it asks for user arg which makes sense but since I am using it with a related obj I wanted to use the related obj as arg so,
user.partner.all() - should use user as arg and fetch the results
user.partner.all(user) - and I should not have to do the below
2 related questions:
1) Does this make sense - should I be doing this?
2) how I can achieve user.partner.all() without adding user in arg
PS: I know i can work with middleware to get_current_user but this function is not reliable as per some of the responses on a different question on SO.
I don't think what you are trying to do will work. Your new situation with a OneToOneField gives you the partner instance.
>>>> user.partner
<Partner xxx>
While in the old situation with the ManyToManyField, the PartnerQuerySet would've been returned.
>>>> user.partner
<PartnerQuerySet []>
A solution would be to create a custom OneToOneField, but this would most probably violate the "simple is better than complex" rule and in the end may even be more work than changing all existing .all()'s.

How to modify Django's Manager QuerySet create() method?

I have two models, Invoice and InvoiceItems, which have a one-to-many relationship.
Throughout the code base we're creating InvoiceItems for a given Invoice using the Manager object as:
invoice.invoice_items.create(...)
The thing is, now we have a validation that has to take place before trying to create an InvoiceItem, and going through the codebase, refactoring all the creation pieces would be a headache.
I wonder if there's a way to override the create method itself or should we go for the model's save()?
To modify a Manager's method you need to create your own. Given the following case:
# models
class MyModel(models.Model):
# ... fields
objects = MyManager()
class MyManager(models.Manager):
def create(self):
# write your own code here
pass
Do not worry about the others methods (filter, delete, etc.) all of them will work as usual.
You can find more about custom managers here

Using Django filters inside model function

The main purpose of a model is to contain business logic, so I want most of my code inside Django model in the form of methods. For example I want to write a method named get_tasks_by_user() inside task model. So that I can access it as
Tasks.get_tasks_by_user(user_id)
Following is my model code:
class Tasks(models.Model):
slug=models.URLField()
user=models.ForeignKey(User)
title=models.CharField(max_length=100)
objects=SearchManager()
def __unicode__(self):
return self.title
days_passed = property(getDaysPassed)
def get_tasks_by_user(self,userid):
return self.filters(user_id=userid)
But this doesn't seems to work, I have used it in view as:
tasks = Tasks.objects.get_tasks_by_user(user_id)
But it gives following error:
'SearchManager' object has no attribute 'get_tasks_by_user'
If I remove objects=SearchManager, then just name of manager in error will change so I think that is not issue. Seems like I am doing some very basic level mistake, how can I do what I am trying to do? I know I can do same thing via :Tasks.objects.filters(user_id=userid) but I want to keep all such logic in model. What is the correct way to do so?
An easy way to do this is by using classmethod decorator to make it a class method. Inside class Tasks:
#classmethod
def get_tasks_by_user(cls, userid):
return cls.objects.filters(user_id=userid)
This way you can simply call:
tasks = Tasks.get_tasks_by_user(user_id)
Alternatively, you can use managers per Tom's answer.
To decided on which one to choose in your specific case, you can refer James Bennett's (the release manager of Django) blog post on when to use managers/classmethod.
Any methods on a model class will only be available to instances of that model, i.e. individual objects.
For your get_tasks_by_user function to be available as you want it (on the collection), it needs to be implemented on the model manager.
class TaskManager(models.Manager):
def get_tasks_by_user(self, user_id):
return super(TaskManager, self).get_query_set().filter(user=user_id)
class Task(models.Model):
# ...
objects = TaskManager()

Unable to load abstract profile model?

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.