Django Add methods to all models - django

I'm creating multiple Django apps. I want to add to every model a function, exist. Can I add this function to the built-in model or do I have to use a proxy model?

if you use proxy you will need to create an class proxy per model in your app. this is no good idea.
how to use proxy
you should use Abstract class.
BaseClass(models.Model):
#here fields option
def do_something(self):
print('Im cool')
class Meta:
abstract = True
SimpleClass(BaseClass):
# here yours fields
class Meta:
abstract = False
AnotherClass(BaseClass):
# here yours fields
class Meta:
abstract = False
simple = SimpleClass()
simple.do_something() #it will print out 'im cool'
another = AnotherClass()
another.do_something() #it will print out 'im cool'
if you want more information about abstract class

Related

Django equivalent of ASP.NET Parameter Binding or Ruby on Rails Action Controller Parameters

I'm wondering what's mentioned in the title. This are links to the examples mentioned, regarding other techs:
ASP.NET Parameter Binding
Ruby on Rails Action Controller
Parameters
Currently I'm building an API using DRF and using custom code in views or serializers validate methods to validate parameters, like this:
class AnimalWriteSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = '__all__'
def validate_dicose_category(self, value):
raise serializers.ValidationError('Dicose Category cannot be set manually.')
Is there a better way?
Since in your example you are telling the serializer to support __all__ fields, then you need to disable updating that one manually.
You probably mean to use use exclude as in the example below, which will simply remove the field from "all". The primary difference between exclude and using read_only is that the output will include the dicose_category.
Use the exclude= to exclude this field. This is the opposite of fields=, and you can only use one at a time.
class AnimalWriteSerializer(serializers.ModelSerializer):
dicose_category = serializers.CharField(read_only=True)
class Meta:
model = Animal
exclude = ["dicose_category"]
You can declare the field as read only (directly or using extra kwarg). You can't write it but it will include be in the output. I'm not sure why you would want to do this, but it can be helpful if you are using the return data for something and need it there.
class AnimalWriteSerializer(serializers.ModelSerializer):
dicose_category = serializers.CharField(read_only=True)
class Meta:
model = Animal
fields = "__all__"
# or declare an extra_kwarg which does the same thing:
class AnimalWriteSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = "__all__"
extra_kwargs = {
"dicose_category": { "read_only": True }
}
And lastly, I strongly suggest listing all the fields you intended to be updated directly, rather than using __all__ or exclude=.
New fields added to the model are not automatically updateable
All updateable fields are explicitly and clearly listed
Unit tests can now be explicit, and the output format is consistent
class AnimalWriteSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = [
"name",
"mission",
"favorite_color",
]

How to create an admin mixin for common/abstract model in Django

I am creating a multi tenant app and have the following abstract class that all relevant tenant specific models inherit from:
class TenantAwareModel(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE)
class Meta:
abstract = True
When registering models in the Django admin, I want to keep my code DRY and not have to add 'tenant' to every single search_fields, list_display, list_filter etc so I tried creating a super class to inherit from.
class TenantAwareAdmin(admin.ModelAdmin):
class Meta:
abstract = True
# change_form_template = "admin/professional_enquiry_change_form.html"
search_fields = ('tenant__id', 'tenant__name', 'tenant__domain',)
list_display = ('tenant',)
list_filter = ('tenant',)
And then trying to inherit that in the registration of the other models. E.g.
class UserAdmin(TenantAwareAdmin, admin.ModelAdmin ):
...
This approach matches the way the TenantAwareModel works.
class TenantAwareModel(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE)
class Meta:
abstract = True
class User(AbstractUser, Uuidable, Timestampable, TenantAwareModel):
...
and it is of course giving me:
django.core.exceptions.ImproperlyConfigured: The model TenantAwareModel is abstract, so it cannot be registered with admin.
Does anybody know the proper way to do this to avoid all the duplicated code?
The Meta class and abstract=True can only be defined in the model definition, not in the ModelAdmin.
The TenantAwareModel is an abstract model that allows you to avoid repeating the common fields in the definition of others models, but you can't register a abstract model in the admin site. The abstract model is not related with any table, its only task is to be a base from which inherit in other models that will represent tables(not abstract models) and which will be able to be registered in the admin site.
To avoid repeat the same fields in search_fields, list_display, etc, you should create a mixin like this:
class TenantAwareAdminMixin:
search_fields = [..., ...]
list_display = [..., ...]
...
and inherit from this
class UserAdmin(TenantAwareAdminMixin, admin.ModelAdmin):
...
But there is a problem... If you override some field defined in the TenantAwareAdminMixin, this will overwrite the value provided by the mixin forcing you to explicitly add the inherited values plus those new values, like this:
class UserAdmin(TenantAwareAdminMixin, admin.ModelAdmin):
list_display = TenantAwareAdminMixin.list_display + ['new_value']
...
It is possible to obtain a similar result by overriding the get_list_display, etc. methods.

How to chain model managers?

I have two abstract models:
class SoftDeleteModel(models.Model):
objects = SoftDeletableManager()
class Meta:
abstract = True
class BookAwareModel(models.Model):
book = models.ForeignKey(Book)
class Meta:
abstract = True
I use often use these models together for DRY purposes, e.g.:
class MyNewModel(SoftDeleteModel, BookAwareModel):
The SoftDeleteModel has a custom manager SoftDeletableManager():
class SoftDeletableManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_removed=False)
If I want to extend the BookAware abstract model to add a queryset filter on Books whilst still preserving the SoftDeletableManager() how would I go about this?
E.g. I can't add objects = BookManager() to BookAwareModel because it will overwrite the SoftDeletableManager.
Having played with your code a bit I came up with three possible solutions which seem to work (according to my tests):
Option 1:
Create a combined manager which is used when defining your concrete MyNewModel and use it for that model:
class CombiManager(SoftDeletableManager, BookAwareManager):
def get_queryset(self):
qs1 = SoftDeletableManager.get_queryset(self)
qs2 = BookAwareManager.get_queryset(self)
return qs1.intersection(qs2)
and then
class MyNewModel(SoftDeleteModel, BookAwareModel):
objects = CombiManager()
Option 2:
Create a Manager for the BookAware model as a subclass of the SoftDeleteableManager
class BookAwareManager(SoftDeletableManager):
def get_queryset(self):
return super().get_queryset().filter(your_filter)
and then add it to your BookAware model with a different name than 'objects':
class BookAwareModel(models.Model):
book = models.ForeignKey(Book)
book_objects = BookAwareManager()
class Meta:
abstract = True
allowing you to get the filtered queryset like
MyNewModel.book_objects.all()
Option 3
Put the BookAwareManager as in Option two as manager into your concrete MyNewModel. Then you can leave the managers name as the default 'objects'

Django Rest Framework: serializer for two models that inherit from a common base abstract model

There is an abstract model that defines an interface for two child models.
I've been asked to create an API endpoint that will return instances from those child models (including only the common fields defined thanks to the interface father class).
The problem raises when defining the Serializer.Meta.model attribute.
Anyway, code is always clearer:
models.py
class Children(Model):
class Meta:
abstract = True
def get_foo(self):
raise NotImplementedError
class Daughter(Children):
def get_foo(self):
return self.xx
class Son(Children):
def get_foo(self):
return self.yy
api/views.py
class ChildrenApiView(ListAPIView):
serializer_class = ChildrenSerializer
def get_queryset(self):
daughters = Daughter.objects.all()
sons = Son.objects.all()
return list(daughters) + list(sons)
serializers.py
class ChildrenSerializer(ModelSerializer):
foo = CharField(source="get_foo", read_only=True)
class Meta:
model = Children # <========= HERE IS THE PROBLEM
fields = ('foo',)
Some thoughts;
I know I'm not able to point out to the abstract model Children (wrote it for showing the inntention)
I tried to leave ChildrenSerializer.Meta.model empty
Seems that I can choose whichever Daughter or Son but not sure if that solution has any side-effect or is the way to go.
Tried to create DaughterSerializer & SonSerializer and use the method get_serializer_class(self) at the view, but wasn't able to make it run
I would probabaly not have a model serializer, and instead have a standard Serializer, with all the fields that you want to return in the view.
This will make it applicable for both Son and Daughter.
So the serializer would be something like:
class ChildrenSerializer(serializers.Serializer):
foo = CharField(source="get_foo", read_only=True)

How to add model field dynamically?

I want to specify a model field dynamically in models.py. For example:
class ModelA(models.Model):
pass # whatever
# immediately after class declaration in models.py
if django_is_not_non_rel:
MyModel.all_modelb = models.ManyToManyField(ModelB, through="ModelAB")
But this code example does not work. When the condition is true, ModelA does not have the all_modelb field after everything is initialized. Why???? How do I effect my intention?
Creating columns dynamically is not impossible with Django but you'll have to do it using a metaclass and/or inheritance.
For your problem the easiest solution would probably be something like this:
if django_is_not_non_rel:
class ModelABase(models.Model):
MyModel.all_modelb = models.ManyToManyField(ModelB, through="ModelAB")
class Meta:
abstract = True
else:
class ModelABase(models.Model):
class Meta:
abstract = True
class ModelA(ModelABase):
pass # whatever
Or even simpler (if you don't need as much customization):
class ModelAORMBase(models.Model):
MyModel.all_modelb = models.ManyToManyField(ModelB, through="ModelAB")
class Meta:
abstract = True
if django_is_not_non_rel:
ModelABase = ModelAORMBase
else:
ModelABase = models.Model
class ModelA(ModelABase):
pass # whatever