Extend Behavior of models.Model - django

In django, I am trying to extend the behavior of the models.Model class.
I want to execute code as the model inits, as well as using the default init.
Here is the code I so far which looks like what im wanting to do but the behavior is incorrect.
class DirtyModel(models.Model):
def __init__(self, *args, **kwargs):
super(DirtyModel, self).__init__(*args, **kwargs)
print("extended")
class Foo(DirtyModel):
bar = models.TextField()
This code tries to make a model called DirtyModel, which I understand why, but I don't suppose I know how to extend the Model class otherwise.
How should I go about creating a custom models.Model class to use in my models?

How should I go about creating a custom models.Model class to use in my models?
Based on your comments, you likely want to create an abstract superclass:
class DirtyModel(models.Model):
def __init__(self, *args, **kwargs):
super(DirtyModel, self).__init__(*args, **kwargs)
print("extended")
class Meta:
abstract = True
By not setting abstract = True, Django will create a DirtyModel as well as a Foo model here, that has an (implicit) OneToOneField to DirtyModel.
If you make it abstract, you basically say that one can not construct a DirtyModel itself, but only some non-abstract subclasses.

Related

How can I set some value to inherited model class field in Django model?

I made one model(ModelA) in which 2 choices present there, I am inheriting this model, in the other two models
CHOICES = (("work", "work"), ("Home", "Home"))
class ModelA(models.Model):
type_of_address = models.CharField(choices=CHOICES)
...
class ForWorkModel(ModelA):
type_of_address--->work
class ForHomeModel(ModelA):
type_of_address--->Home
I want to inherit the model and want to set some field values, as I mentioned in the code.
Is there any way?
You could add a custom save on each of the individual models to set the desired string:
class ForWorkModel(ModelA):
def save(self, *args, **kwargs):
if not self.pk: # only change if the object is new
self.type_of_address = "work" # or CHOICES[0][0]
super().save(*args, **kwargs)
or, I'm not 100% sure, but this might work:
class ModelA(models.Model):
type_of_address = models.CharField(choices=CHOICES, default=cls.get_default_address)
class ForWorkModel(ModelA):
def get_default_address():
return "work"

Getting a Model dynamic by knowing its name

I use Django 2.1.
I have a general utility/function(using celery) that I call from the save method of a Model(is an abstract Model, inherited by other Models).
Because the data from the function will be serialize, I pass to it the Model name and PK.
class A(models.Model)
def save(self, *args, **kwargs):
send_async(model_name=type(self).__name__, pk=self.id))
class B(A)
class C(A)
Later, I want to query the Model, but I don't know how to get the Model:
I tried:
model = apps.get_model('django_app_name.{}'.format(model_name))
I get the following error:
No installed app with label 'django_app_name'.
Also:
model = ContentType.objects.get(model=model_name)
This doesn't trow an error, but very strange behavior 'recalls' the View with different arguments, and off course no results.
Use the name of the app which has the model, not django_app_name.
To get the model name, try this:
send_async(model_name=self.__class__.__name__, pk=self.id)
instance.__class__.__name__ will return the class name
You should do like so:
class A(models.Model)
def save(self, *args, **kwargs):
send_async(
model_name=self._meta.model_name,
app_label=self._meta.app_label,
pk=self.id
)
And in send_async:
def send_async(model_name, app_label, pk):
content_type = ContentType.objects.get(app_label=app_label, model=model_name)
instance = content_type.get_object_for_this_type(id=pk)
...
For more information about it see https://docs.djangoproject.com/en/2.1/ref/contrib/contenttypes/

Listing only usable values in OneToOneField Django

I want to list only usable items in OneToOneField not all items, its not like filtering values in ChoiceField because we need to find out only values which can be used which is based on the principle that whether it has been used already or not.
I am having a model definition as following:
class Foo(models.Model):
somefield = models.CharField(max_length=12)
class Bar(models.Model):
somefield = models.CharField(max_length=12)
foo = models.OneToOneField(Foo)
Now I am using a ModelForm to create forms based on Bar model as:
class BarForm(ModelForm):
class Meta:
model = Bar
Now the problem is in the form it shows list of all the Foo objects available in database in the ChoiceField using the select widget of HTML, since the field is OneToOneField django will force to single association of Bar object to Foo object, but since it shows all usable and unusable items in the list it becomes difficult to find out which values will be acceptable in the form and users are forced to use hit/trial method to find out the right option.
How can I change this behavior and list only those items in the field which can be used ?
Although this is an old topic I came across it looking for the same answer.
Specifically for the OP:
Adjust your BarForm so it looks like:
class BarForm(ModelForm):
class Meta:
model = Bar
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
#only provide Foos that are not already linked to a Bar, plus the Foo that was already chosen for this Bar
self.fields['foo'].queryset = Foo.objects.filter(Q(bar__isnull=True)|Q(bar=self.instance))
That should do the trick. You overwrite the init function so you can edit the foo field in the form, supplying it with a more specific queryset of available Foo's AND (rather important) the Foo that was already selected.
For my own case
My original question was: How to only display available Users on a OneToOne relation?
The Actor model in my models.py looks like this:
class Actor(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name = 'peactor')
# lots of other fields and some methods here
In my admin.py I have the following class:
class ActorAdmin(admin.ModelAdmin):
# some defines for list_display, actions etc here
form = ActorForm
I was not using a special form before (just relying on the basic ModelForm that Django supplies by default for a ModelAdmin) but I needed it for the following fix to the problem.
So, finally, in my forms.py I have:
class ActorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ActorForm, self).__init__(*args, **kwargs)
#only provide users that are not already linked to an actor, plus the user that was already chosen for this Actor
self.fields['user'].queryset = User.objects.filter(Q(peactor__isnull=True)|Q(peactor=self.instance))
So here I make an ActorForm and overwrite the __init__ method.
self.fields['user'].queryset =
Sets the queryset to be used by the user formfield. This formfield is a ModelChoiceField
by default for a OneToOneField (or ForeignKey) on a model.
Q(peactor__isnull=True)|Q(peactor=self.instance)
The Q is for Q-objects that help with "complex" queries like an or statement.
So this query says: where peactor is not set OR where peactor is the same as was already selected for this actor
peactor being the related_name for the Actor.
This way you only get the users that are available but also the one that is unavailable because it is already linked to the object you're currently editing.
I hope this helps someone with the same question. :-)
You need something like this in the init() method of your form.
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
# returns Bar(s) who are not in Foo(s).
self.fields['foo'].queryset = Bar.objects.exclude(id__in=Foo.objects.all().values_list(
'bar_id', flat=True))
PS: Code not tested.

Django abstract models with M2M fields

Let's suppose I have the following:
class Base(Model):
m2m_1 = ManyToManyField("SomeModel1")
m2m_2 = ManyToManyField("SomeModel2")
class Meta:
abstract = True
class A(Base):
def __init__(self):
super(A, self).__init__()
pass
class B(Base):
def __init__(self):
super(B, self).__init__()
pass
However, I cannot do that because it requires related name for M2M field. However, that does not help as the model is abstract and django tries to create the same related name for both A and B models.
Any ideas how to specify related names for each model separately or even do not use them at all?
The answer is right in the docs for abstract classes (under section entitled "Be careful with related_name"):
m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")

Django Problem inheriting formfield_callback in ModelForms

I've only been using Django for a couple of weeks now, so I may be approaching this all kinds of wrong, but:
I have a base ModelForm that I put some boilerplate stuff in to keep things as DRY as possible, and all of my actual ModelForms just subclass that base form. This is working great for error_css_class = 'error' and required_css_class = 'required' but formfield_callback = add_css_classes isn't working like I would expect it to.
forms.py
# snippet I found
def add_css_classes(f, **kwargs):
field = f.formfield(**kwargs)
if field and 'class' not in field.widget.attrs:
field.widget.attrs['class'] = '%s' % field.__class__.__name__.lower()
return field
class BaseForm(forms.ModelForm):
formfield_callback = add_css_classes # not working
error_css_class = 'error'
required_css_class = 'required'
class Meta:
pass
class TimeLogForm(BaseForm):
# I want the next line to be in the parent class
# formfield_callback = add_css_classes
class Meta(BaseForm.Meta):
model = TimeLog
The end goal is to slap some jquery datetime pickers on forms with a class of datefield/timefield/datetimefield. I want all of the date time fields within the app to use the same widget, so I opted to do it this way than explicitly doing it for each field in every model. Adding an extra line to each form class isn't that big of a deal, but it just bugged me that I couldn't figure it out. Digging around in the django source showed this is probably doing something I'm not understanding:
django.forms.models
class ModelFormMetaclass(type):
def __new__(cls, name, bases, attrs):
formfield_callback = attrs.pop('formfield_callback', None)
But I don't know how __init__ and __new__ are all intermangled. In BaseForm I tried overriding __init__ and setting formfield_callback before and after the call to super, but I'm guessing it needs to be somewhere in args or kwargs.
__new__ is called before object construction. Actually this is a factory method that returns the instance of a newly constructed object.
So there there are 3 key lines in ModelFormMetaclass:
formfield_callback = attrs.pop('formfield_callback', None) #1
fields = fields_for_model(opts.model, opts.fields,
opts.exclude, opts.widgets, formfield_callback) #2
new_class.base_fields = fields #3
In the class we attach base_fields to our form.
Now let's look to ModelForm class:
class ModelForm(BaseModelForm):
__metaclass__ = ModelFormMetaclass
This means that ModelFormMetaclass.__new__(...) will be called when we create a ModelForm instance to change the structure of the future instance. And attrs of __new__ (def __new__(cls, name, bases, attrs)) in ModelFormMetaclass is a dict of all attributes of ModelForm class.
So decision is to create new InheritedFormMetaclass for our case (inheriting it from ModelFormMetaclass). Don't forget to call new of the parent in InheritedFormMetaclass. Then create our BaseForm class and say:
__metaclass__ = InheritedFormMetaclass
In __new__(...) implementation of InheritedFormMetaclass we could do all we want.
If my answer is not detailed enough please let me know with help of comments.
You may set widgets class like this:
class TimeLogForm(BaseForm):
# I want the next line to be in the parent class
# formfield_callback = add_css_classes
class Meta(BaseForm.Meta):
model = TimeLog
widgets = {
'some_fields' : SomeWidgets(attrs={'class' : 'myclass'})
}
For what you're trying to accomplish, I think you're better off just looping through the fields on form init. For example,
class BaseForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BaseForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'error'
Clearly you'll need a little more logic for your specific case. If you want to use the approach that sergzach suggested (overkill for your particular problem I think), here's some code for you that will call formfield_callback on the base class in the case the subclass doesn't define it.
baseform_formfield_callback(field):
# do some stuff
return field.formfield()
class BaseModelFormMetaclass(forms.models.ModelFormMetaclass):
def __new__(cls, name, bases, attrs):
if not attrs.has_key('formfield_callback'):
attrs['formfield_callback'] = baseform_formfield_callback
new_class = super(BaseModelFormMetaclass, cls).__new__(
cls, name, bases, attrs)
return new_class
class BaseModelForm(forms.ModelForm):
__metaclass__ = OrganizationModelFormMetaclass
# other form stuff
Finally, you might wanna look into crispy forms: https://github.com/maraujop/django-crispy-forms
sergzach is correct that you have to use metaclasses; overriding __init__ is not enough. The reason is that the metaclass for ModelForm (which will be called for all ModelForm subclasses unless you specify another metaclass in a subclass) takes the class definition, and using the values in the class definition creates a class with class attributes. For example, both META.fields and our formfield_callback is used to create form Fields with various option (like which widget).
That means AFAIU formfield_callback is a parameter to the metaclass used when creating your custom model form class, not some value used at runtime when actual form instances are created. That makes placing formfield_callback in __init__ useless.
I solved a similiar problem with a custom metaclass like
from django.forms.models import ModelFormMetaclass
class MyModelFormMetaclass(ModelFormMetaclass):
def __new__(cls,name,bases,attrs):
attrs['formfield_callback']=my_callback_function
return super(MyModelFormMetaclass,cls).__new__(cls,name,bases,attrs)
and in the base class for all my model forms setting the metaclass
class MyBaseModelForm(ModelForm):
__metaclass__=MyModelFormMetaclass
...
which can be used like (at least in Django 1.6)
class MyConcreteModelForm(MyBaseModelForm):
# no need setting formfield_callback here
...