Regarding Base and Child Models: Django - django

I have a scenario in which I want the application to fail at the compile/build time if the child model does not have sub_field_a.
Only models which have sub_field_a should be able to inherit from Base model otherwise the application should fail at compile/build time.
class Base(models.Model):
class Meta:
abstract = True
class SubA(Base):
sub_field_a = models.CharField(max_length=255)
sub_field_c = models.CharField(max_length=255)
class SubB(Base):
sub_field_b = models.CharField(max_length=255)
sub_field_c = models.CharField(max_length=255)
Here, I should get an error because SubB does not have sub_field_a
How can I achieve this? TIA

Assuming you are using Django 1.7 or above, you should be able to use the system check framework; specifically, the field, model and manager checks.
I believe it would look something like this:
from django.core import checks
from django.db import models
class Base(models.Model):
class Meta:
abstract = True
#classmethod
def check(self, **kwargs):
errors = super(Base, self).check(**kwargs)
if not self._meta.abstract and not hasattr(self, 'sub_field_a'):
errors.extend([
checks.Error(
'missing sub_field_a',
hint='Add a field named sub_field_a',
obj=self,
id='myapp.E001',
)
])
return errors

Isn't the whole point of inheritance is to reduce and reuse common code. In this case we know sub_field_a always need to be defined. In that case why not add it to the base model like this:
class Base(models.Model):
sub_field_a = models.CharField(max_length=255)
class Meta:
abstract = True
class SubA(Base):
sub_field_c = models.CharField(max_length=255)
class SubB(Base):
sub_field_b = models.CharField(max_length=255)
sub_field_c = models.CharField(max_length=255)
This totally invalidates the use case that is required here. Hope this helps :)

Related

Python / django.db: How to create a generic class without hardcoded Meta.db_table?

I have a class using django.db as follows:
from django.db import models
class myClass(models.Model):
"My class"
class Meta:
db_table = 'my_table'
field1 = models.CharField()
# more fields here
def some_function(self):
return "hello"
I'd like to turn this into a generic class that does not have "my_table" hardcoded, so that I can use it in some way such as:
class GenericClass(models.Model):
class Meta:
db_table = None
# genericClass properties and methods here
class myClass(GenericClass):
id = models.CharField(max_length=20)
def __init__(self, *args, **kwargs):
super(self.__class__, self).Meta.db_table = 'my_table'
super(self.__class__, self).__init__(*args, **kwargs)
However, this gives me the following error (using Python 2.7 / Django 1.11):
Local field u'id' in class 'myClass' clashes with field of similar name from base class 'GenericClass'
Is there any way to create a generic class that extends django.db.models.Model, that supports a non-default database table name without hardcoding Meta.db_table?
I guess what you are asking is a Django equivalent of AbstractSuperClass / SuperClass.
If what you want is AbstractSuperClass for django model you can define it like
class AbstractModel(models.Model):
field1 = ...
field2 = ...
...
class Meta:
abstract = True
class SubClassModel(models.Model):
field3 = ...
class Meta:
db_table=sub_class_db_table
Your AbstractModel won't have a database table. It simply acts as a superclass to store common fields (and even you can define common functions).
If you DESC your sub_class_db_table, you will find field1, field2, field3.
See this for more details.
If you want to create a db_table hierarchy itself (field1 and field2 will be in the super_class_table and field3 will be in sub_class_table), you can use Multi-table inheritance. This will be useful especially if there is a service-contract scenario - A team or a module owns common functionality including the db_table, its maintenance. But this way is less commonly used.

Serialize relation both ways with Django rest_framework

I wonder how to serialize the mutual relation between objects both ways with "djangorestframework". Currently, the relation only shows one way with this:
class MyPolys(models.Model):
name = models.CharField(max_length=20)
text = models.TextField()
poly = models.PolygonField()
class MyPages2(models.Model):
name = models.CharField(max_length=20)
body = models.TextField()
mypolys = models.ManyToManyField(MyPolys)
# ...
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPages2
# ...
class MyPolyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPolys.objects.all()
serializer_class = srlz.MyPolysSerializer
class MyPages2ViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPages2.objects.all()
serializer_class = srlz.MyPages2Serializer
The many-to-many relation shows up just fine in the api for MyPages2 but nor for MyPolys. How do I make rest_framework aware that the relation goes both ways and needs to be serialized both ways?
The question also applies to one-to-many relations btw.
So far, from reading the documentation and googling, I can't figure out how do that.
Just do it like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields =('id','name','text','poly')
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
mypolys = MyPolysSerializer(many=True,read_only=True)
class Meta:
model = testmodels.MyPages2
fields =('id','name','body','mypolys')
I figured it out! It appears that by adding a mypolys = models.ManyToManyField(MyPolys) to the MyPages2 class, Django has indeed automatically added a similar field called mypages2_set to the MyPolys class, so the serializer looks like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields = ('name', 'text', 'id', 'url', 'mypages2_set')
I found out by inspecting an instance of the class in the shell using ./manage.py shell:
pol = testmodels.MyPolys.objects.get(pk=1)
pol. # hit the tab key after '.'
Hitting the tab key after the '.' reveals additional fields and methods including mypages2_set.

django inlines with abstract model give error

I have the following in Django models.py (models are stripped down to only necessary fields)
class Product(BaseProduct):
price = models.IntegerField()
productfoto = models.ManyToManyField("ProductFoto", related_name="%(app_label)s_%(class)s_related")
class Meta:
abstract = True
ordering = ['name']
# Inherits Product class
class ConsumerProduct(Product):
categorie= models.ForeignKey(Categorie)
class Meta:
verbose_name_plural = "ConsumerProducten"
class ProductFoto(models.Model):
myimage= FileBrowseField("Image", max_length=200, directory='producten')
and this in admins.py:
class ProductFotoInline(admin.TabularInline):
extra = 1
model = Product.productfoto.through
class ConsumerProductAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("name",)}
inlines= [ProductFotoInline]
admin.site.register(ConsumerProduct, ConsumerProductAdmin)
Please take note of following:
Product class is abstract
ConsumerProduct inherits Product
I would say this should work, however I'm getting the following ImproperlyConfigured error when trying to add a new ConsumerProduct:
'model' is a required attribute of 'ConsumerProductAdmin.inlines[0]'.
Any help is appreciated
I've decided to make my Product class non-abstract. By removing the registration of this class with the admin (removing the line admin.site.register(Product))
You will not see it in the backend, however in your database, it will be a bit more messy, because you will have both a Product and a ConsumerProduct table

Django - Foreign key reverse creation

Imagine this model:
class ExercisePart(models.Model):
exercise = models.ForeignKey(Exercise)
sequence = models.IntegerField()
class Meta:
unique_together = (('exercise', 'sequence',),)
class Exercise(models.Model):
name = models.CharField(max_length=15)
From the admin interface I'd like to be able to create/link ExerciseParts through the Exercise page. I'd like to do that because I wish to avoid having to go on another page each time I want to add an ExerciseParts.
Is it possible ? How can I do that ?
You're looking for the inline admin feature.
admin.py
class ExercisePartInline(admin.TabularInline):
model = ExercisePart
class ExerciseAdmin(admin.ModelAdmin):
inlines = [ExercisePartInline]

Can I add a manager to a manytomany relationship?

I have two models that has a manytomany relationship with a 'through' table in some way?
class Bike(models.Model):
nickname = models.CharField(max_length=40)
users = models.ManyToManyField(User, through='bike.BikeUser')
The BikeUser class
class BikeUser(models.Model):
bike = models.ForeignKey(Bike)
user = models.ForeignKey(User)
comment = models.CharField(max_length=140)
I would like to add functionality to the Bike class for working with users, is there a best practice way of doing this. I would like to avoid adding too many methods to the Bike class and rather have some kind of manager to work through
Something like:
bike.bikeusers_set.commonMethod()
or
bike.bikeusers.commonMethod()
What would be the best way to accomplish this?
Once you have the BikeUser model, you can add a custom manager to the model.
Something like:
class BikeUserManager(models.Manager):
def commonMethod():
pass
class BikeUser(models.Model):
bike = models.ForeignKey(Bike)
user = models.ForeignKey(User)
comment = models.CharField(max_length=140)
objects = BikeUserManager()
But you can only use it from the BikeUser Model:
BikeUser.objects.commonMethod()
What you want is to use this manager as a related manager:
http://docs.djangoproject.com/en/dev/topics/db/managers/#controlling-automatic-manager-types
Add the use_for_related_fields=True to the manager class.
class MyManager(models.Manager):
use_for_related_fields = True
use_for_related_fields is deprecated from django 2.0. Use for related fields is possible via base manager.
old:
class CustomManager(models.Model):
use_for_related_fields = True
class Model(models.Model):
custom_manager = CustomManager()
new:
class Model(models.Model):
custom_manager = CustomManager()
class Meta:
base_manager_name = 'custom_manager'
source of example
Remember about restrictions for get_queryset() method.
Code
def m2m_with_manager(related_manager, model_manager_name: str):
"""Replaces the default model manager tied to related manager with defined"""
model_manager = getattr(related_manager.model, model_manager_name)
return model_manager.filter(**related_manager.core_filters)
Example
for class Author and Books, where one Author can have multiple books
class Book:
author = FK(Author, related_name='books')
best = models.Manager(...)
Usage
wanted_qs = m2m_with_manager(author.books, model_manager_name='best')