I am trying to create a very simple CRUD application using REST API.
So I create a very simple model, serializer and viewset for all these.
And then I noticed that I don't fully understand some basic principals about right use-cases for calling (for example create method for my model instance)
As I understand, django providers several approaches:
I can define my CRUD methods inside model class:
class Foo(models.Model):
...
def create(...):
foo = Foo()
foo.save()
I also can create instances using model serializers (seems there is no big difference, because the same save method from model instance is calling):
class FooSerializer(seializer.ModelSerilizer):
...
class Meta:
model = Foo
....
def create():
fs = self.Meta.model()
fs.save()
2b. I can use simple serializers:
class FooSerializer(serializers.Serializer):
def create(**validated_data):
return Foo(**validated_data)
Finally I can use perform_create, update and so on from viewset:
class FooView(ModelViewSet):
serializer = FooSerializer
def perform_create():
serializer.save()
...
Is there some patterns when one or another solution should be implemented?
Could you please provide some explanation with use cases?
THanks!
Lets go step by step on your points of creating/using create method:
You don't need to write a create() method inside model.
You don't need to write a create() method in model serializer, unless you want to handle additional keywords or override the create() method to change the default behavior(Reference).
In serializer.Serializer you can write a create() method if you want save an instance with that serializer. Useful when you are using this serializer with GenericAPIViews or Viewsets. Reference can be found in documentation.
By writing perform_create() method in Viewset, you are basically overriding the default perform_create() from the Viewset. You can integrate additional tasks inside that function when overriding it(example).
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 the following custom model manager in Django that is meant to count the number of related comments and add them to the objects query set:
class PublicationManager(models.Manager):
def with_counts(self):
return self.annotate(
count_comments=Coalesce(models.Count('comment'), 0)
)
Adding this manager to the model does not automatically add the extra field in DRF. In my API view, I found a way to retrieve the count_comments field by overriding the get function such as:
class PublicationDetails(generics.RetrieveUpdateAPIView):
queryset = Publication.objects.with_counts()
...
def get(self, request, pk):
queryset = self.get_queryset()
serializer = self.serializer_class(queryset.get(id=pk))
data = {**serializer.data}
data['count_comments'] = queryset.get(id=pk).count_comments
return Response(data)
This works for a single instance, but when I try to apply this to a paginated list view using pagination_class, overriding the get method seems to remove pagination functionality (i.e. I get a list of results instead of the usual page object with previous, next, etc.). This leads me to believe I'm doing something wrong: should I be adding the custom manager's extra field to the serializer instead? I'm not sure how to proceed given that I'm using a model serializer. Should I be using a basic serializer?
Update
As it turns out, I was using the model manager all wrong. I didn't understand the idea of table-level functionality when what I really wanted was row-level functionality to count the number of comments related to a single instance. I am now using a custom get_paginated_response method with Comment.objects.filter(publication=publication).count().
Original answer
I ended up solving this problem by creating a custom pagination class and overriding the get_paginated_response method.
class PaginationPublication(pagination.PageNumberPagination):
def get_paginated_response(self, data):
for item in data:
publication = Publication.objects.with_counts().get(id=item['id'])
item['count_comments'] = publication.count_comments
return super().get_paginated_response(data)
Not sure it's the most efficient solution, but it works!
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
For example, see the code below:
class Thing(Model):
def save(force=False, *args, **kwargs):
if not force:
raise Exception("don't save!")
FactoryBoy calls model.get_or_create() which calls model.save(). Is there any way to create an instance of this model in FactoryBoy without modifying the save or get_or_create methods?
If all you want is a local instance of a Django model, you should use the build strategy. Also, the create strategy invokes _create method, which can be overwritten to meet your needs.
The default behavior of factory.django.DjangoModelFactory is to call MyModel.objects.create().
If the goal is only for a single call in one test, just use MyModelFactory.build().
If the goal is to never call create(), set the following in your declaration:
class MyModelFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.MyModel
strategy = factory.BUILD_STRATEGY
This maps MyModelFactory() to MyModelFactory.build() instead of the DjangoModelFactory default, MyModelFactory.create().
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()