How to set initialisation value on abstract Django mixin from concrete class - python

I have an abstract mixin class that adds a Django model field to any concrete class that inherits from it.
At class initialisation - when makemigrations is run - I'd like the inheriting class to define whether an inherited field is required or optional via the blank= True or False property.
I've tried various Meta and __new__ approaches, but can't figure out how the abstract mixin class can get the information from the inheriting class.
Here's a naive attempt:
from django.db import models
class DescriptionMixin(models.Model):
class Meta:
abstract = True
description = models.TetxField(
# how to get value here?
blank=inheriting_class.description_required
)
class OptionalDescription(DescriptionMixin, SomeOtherClass):
class Meta:
verbose_name = 'Optional description'
description_required = False
class RequiredDescription(DescriptionMixin, SomeOtherClass):
class Meta:
verbose_name = 'Required description'
description_required = True
Thanks in advance for any help offered.

You can't do this at the database level. makemigrations doesn't actually initialise your models to create the migration files.
But since you're trying to enforce this on a TextField, which cannot be enforced at the database level anyway (blank is only used when validating a model through the full_clean() method), you could just override the clean() method on the DescriptionMixin, checking the value of self.blank and raising a ValidationError appropriately.

Solved using this (it's actually Wagtail on top of Django):
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
self._meta.get_field('description').blank = not getattr(self, 'description_required', False)

Related

What is the use of class Meta in django?

I have been using django and used class Meta: a lot of times, actually what is the use of it?
for example , In django models
class Accounts(models.Model):
---some code here---
class Meta:
ordering = [-1]
In django forms
class AccountForm(forms.ModelForm):
---some code here---
class Meta:
fields = '__all__'
class Meta is basically the inner class. In Django, the use of the Meta class is simply to provide metadata to the ModelForm or the Model class. It is different from the metaclass of a Class in Python.
class Meta is used to change the behavior of the models such ordering, verbose_name etc. Though it is optional to be included in your models.

Class reference inside class scope in python

Some Context
I have the following django/python snippet:
from rest_framework import serializers
from .models import Profile, Task
class Serializable():
types = {}
def __init__(self, objectid):
self.object = self.types[objectid][0]
self.serializer = self.types[objectid][1]
def serialized(self):
instances = self.object.objects.all()
serialized = self.serializer(instances, many=True)
return serialized
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
oid = 'profile'
model = Profile
fields = ['login', 'status']
Serializable.types[oid] = [model, <class-reference>]
class TaskSerializer(serializers.ModelSerializer):
class Meta:
oid = 'task'
model = Task
fields = ['description', 'date', 'owner']
Serializable.types[oid] = [model, <class-reference>]
I am using Django with the rest_framework library installed. One of the interesting features I am using is ModelSerializers (ModelSerializers Documentation), which save quite a lot of code repetition. I want Serializable.types variable to be populated on runtime (when all the serializer classes are declared). The whole point of this is that I will not have to update my views whens a new type of model is included. For example, I would print the json representation of my model instances like this:
class QueryObject(APIView):
permission_classes = (AllowAny,)
def get(self, request, *args, **kwargs):
oid = request.GET['oid']
serializable= Serializable(oid)
json = serializable.serialized
return JsonResponse(json)
The Problem
The major problem is in the last line of each Serializer class.
Serializable.types[oid] = [model, <class-reference>]
I've tried putting the name of the class, ProfileSerializer for example, to no avail. I've tried doing the same outside of the Meta class, such as:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
oid = 'profile'
model = Profile
fields = ['login', 'status']
Serializable.types[Meta.oid] = [Meta.model, ProfileSerializer]
also not successful. Not sure what else to do, which is why I'm hoping the community can help me on this one.
This is actually a case for defining a metaclass.
I've never actually found a source of information which gives a complete, clear and satisfactory explanation as to what metaclasses are or how they work. I will try to enhance this answer with such information if required but for the time being I am going to stick to a solution for your present problem. I am assuming python 3.
Define an additional class, thus:
class ModelSerializerMeta(serializers.SerializerMetaclass):
def __init__(cls, class_name, base_classes, attributes):
super(ModelSerialiserMeta, cls).__init__(class_name, base_classes, attributes)
Serializer.types[cls.Meta.oid] = [cls.Meta.model, cls]
Then use this as the metaclass of your Serializers, e.g.
class ProfileSerializer(serializers.ModelSerializer, metaclass=ModelSerializerMeta):
class Meta:
oid = 'profile'
model = Profile
fields = ['login', 'status']
Better yet, create some superclass for all your model serializers, assign the metaclass there, make all of your serializers inherit from that superclass which will then use the metaclass throughout.
Metaclasses are definitely the right answer unless your code can require python >= 3.6. Starting with 3.6 there is a new feature called the __init_subclass__ hook.
So you can do something like
class foo:
#classmethod
def __init_subclass__(cls, *args, **kwargs):
Serializers.register_class(cls)
Whenever a child of Foo is defined, the __init_subclass__ method on Foo will be called, passing in the child class reference as cls.

Django: access the model meta class value

I have some model classes defined:
class ModelA(models.Model):
class Meta:
abstract = True
class ModelB(ModelA):
class Meta:
abstract = False
So, now I have a class object, I want to check if it is abstract, is there any way to do this?
For example, I want something like:
>>> ModelA.abstract
True
>>> ModelB.abstract
False
Oh, I found that it is easy to get the Meta class by _meta field of the class:
>>> ModelA._meta.abstract
True
technically, strictly speaking, no external method should access a method or property that begins with an underscore (_) as it is private and protected from outsider access. To solve this, in your model file, add a property:
class Foo(models.Model):
class Meta:
verbose_name = "Foolish"
#property
def verbose_name(self):
return self._meta.verbose_name
Then your view can "properly" access the Meta.verbose_name via Foo().verbose_name as opposed to Foo._meta.verbose_name

How to call only a subset of validation methods when editting

In my Django app, I am using a model (let's call it Mymodel), and a form :
class Mymodel(models.Model):
firstField(...)
secondField(...)
class MymodelAddform(ModelForm):
def clean_firstField(self):
#stuff
def clean_secondField(self):
#stuff again
def clean(self):
#performs stuff with all the fields
class Meta:
model = Mymodel
Now I want to add another form, MymodelEditform, based on Mymodel again, using only secondField, and only secondField validation
Two options I've considered (both do not work as I wrote them) :
class MymodelEditform(ModelForm):
class Meta:
model = Mymodel
fields = ['secondField']
Here the problem is that clean_secondField is not called unless I re-define it, and I would like to avoid having clean_secondField call another method defined elsewhere (though, if it is the only option, so be it)
class MymodelEditform(MymodelAddform):
class Meta:
model = Mymodel
fields = ['secondField']
Here the problem is that the clean() validation is called, and since I'm only using a subset of the fields, it fails.
The question is pretty obvious : how can I make it work as intended?
I haven't done this, but you can try this.
Create a simple class with clean methods.
As below
class MymodelformCleaner(ModelForm):
def clean_firstField(self):
#stuff
def clean_secondField(self):
#stuff again
Inherit your model forms from this class
Your model forms will just define the fields, while clean methods come from another class
class MymodelAddform(ModelForm, MymodelformCleaner):
class Meta:
model = Mymodel
class MymodelEditform(ModelForm, MymodelformCleaner):
class Meta:
model = Mymodel
fields = ['secondField']
An obvious solution would be to define clean_secondField in MymodelEditform and make MyModelAddForm inherit from MymodelEditForm, but it might not work as expected. Another solution would be to make both forms inherit from a common base form defining clean_secondField.
Or you could just explicitely exclude the field in the form's Meta (cf https://code.djangoproject.com/ticket/12901)

Django forms.ModelForm, Pylint, and new/old style classes

I have a Django 1.5 form that looks like this (simplified):
class BidForm(forms.ModelForm):
class Meta:
fields = (
)
model = Bid
def __init__(self, *args, **kwargs):
super(BidForm, self).__init__(*args, **kwargs)
something()
When I run Pylint on this, I get a this error:
E1002:<line,row>:BidForm.__init__: Use of super on an old style class
I assume this means the Django's forms.ModelForm is an old-style class and per the python docs my call to super is not happening and is therefore extraneous. Is this true? Can I just delete the super call without effect?
No. Pylint, great though it is, is far from infallible, and in this case has just got it wrong. ModelForm is a new style class and the super is needed.
This error/warning has nothing to do with the ModelForm class and has to do with:
class Meta:
fields = ()
model = Bid
You just need to suppress the warning:
class Meta: # pylint: disable=C1001
fields = ()
model = Bid

Categories