I have an abstract model that is inherited by 2 children. In the children, one is setting a validator. When I run the code, I see both children having the validator.
Here is the pseudo code:
class myAbstract(models.Model):
Intro = models.TextField(blank=True, null=True, )
class Meta:
abstract = True
class child1(myAbstract):
class Meta:
verbose_name = 'child 1'
class child2(myAbstract):
def __init__(self, *args, **kwargs):
super(child2, self).__init__(*args, **kwargs)
intro = self._meta.get_field('Intro')
intro.validators.append(MaxLengthValidator(60))
class Meta:
verbose_name = 'child 2'
In the admin if I add a child1 and then add a child2 then the validator kicks in for child2 and limits the number of characters. If I start with child2 then child2 doesn't get the validator.
Is this the expected behavior? If there, what is the suggested way of coding this? I thought about moving Intro to the child classes.
Solved:
As Alasdair pointed out the validators is a class variable therefore this is the expected behavior.
I tried moving the Intro field to the child but that didn't work. I used this solution:
https://stackoverflow.com/a/3209550/757955 that sets forms.CharField in the modelform.
The validators are not set per model instance. When you append the MaxLengthValidator, you are altering the intro field of the parent class.
I don't think there's an easy way around this. You could write a clean() method for each child model, and perform the validation there. However, I think that moving the intro field into the child classes is probably the best option here.
I did not really expect this behaviour, but there is a lot of magic happenign in the background with models. I see 2 solutions:
Change the instance, not the class. _meta is an class variable, thus _meta.get_field will return class attributes. I would rather try to manipulate the instance fields like so
def init(...):
self.intro.validators.append(MaxLengthValidator(60))
If 1 does not work, or you do not like it, leave the models alone, i.e. do not add the validator, but add the validators to the form that you use for the models. There you have more flexibility and can do what you want.
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 we have this models in django:
class FotherModel(models.Model):
# Some fields goes here!
class Meta:
# Some fields goes here!
abstract = True
class ChildModel(FotherModel):
# Some fields goes here!
class Meta(FotherModel.Meta):
#s Some fields goes here!
When we inherit a field from the meta class of Django models, that field appears in child meta class, But this rule does not apply to abstract=True.
I know that if this happens, no table in database will be created, But I don't know how this inheritance didn't happen. Please explain this process for me.
The Model metaclass resets abstract in a model's Meta class. In this document you can see:
Django does make one adjustment to the Meta class of an abstract base
class: before installing the Meta attribute, it sets abstract=False.
This means that children of abstract base classes don’t automatically
become abstract classes themselves.
Also, you can see the source code of this process in this link:
if abstract:
# Abstract base models can't be instantiated and don't appear in
# the list of models for an app. We do the final setup for them a
# little differently from normal models.
attr_meta.abstract = False
new_class.Meta = attr_meta
return new_class
Because of the concept and effect of some fields in meta section in many cases it doesn't make sense that the field is inherited by chidren.
It has been described here
I'm using Python 3.6+PostgreSQL 10+latest Django and DjangoRestFRamework. I have the following models, in which several models inherit from a class which is the ForeignKey (One-to-Many) of another class.
class Voteable(models.Model):
Voteable_id = models.BigAutoField(primary_key=True);
class base(Voteable):
class Meta:
abstract = False
class traslated_info(models.Model):
info_about=models.ForeignKey(base)
info_body=models.TextField()
info_language=models.CharField(max_length=2)
class A(base):
A_id=models.BigAutoField(primary_key=True)
A_field=models.TextField()
class B(base):
B_id=models.BigAutoField(primary_key=True)
B_field=models.TextField()
B_belongs_to=models.ForeignKey(A)
class C(base):
C_id=models.BigAutoField(primary_key=True)
C_field=models.TextField()
C_belongs_to=models.ForeignKey(A)
C_belongs_to=models.ForeignKey(B)
Whenever I try saving an object A (via curl), django says that base_ptr is required. I don't know how to model this situation. The end user is not expected to create item base and then item A, B or C. I tried class base as abstract, but an abstract class can't be ForeignKey. I want to automatically create a base class whenever a class A is created.
I think I have two options: A) Remove the ForeignKey and store the language-specific info fields as HStoreField. This makes the code somewhate dependent on Postgree. B) Create some sort of routine that automatically creates parent base item whenever a child A item is created (preserving the one to one relationship).
What do you recommend? Is there some django easy option I'm missing to make option B? I have not found this. Thank you.
Having an autofield as primary_key in the models A, B or C causes this error, as creating a child model doesn't cascade-create parents.
I found two workarounds:
Change autofield option primary_key to false and add
SILENCED_SYSTEM_CHECKS=['fields.E100']
Overriding Viewset create method:
#transaction.atomic
def create(self,request,*args,**kwargs):
request.data['base_ptr'] = base.objects.create(user=request.user,created_date=datetime.utcnow()).pk
return super(viewsets.ModelViewSet,self).create(request,*args,**kwargs)
I will stick with the second, I'm quite sure more issues will arise.
Make your serializer as below, you dont need to create base classes explicitly, it will be created automatically.
class ASerializer(serializers.ModelSerializer):
class Meta:
model = A
read_only_fields = ('base_ptr',)
fields = '__all__'
I have the following models:
class Person(models.Model):
# fields
class Teacher(Person):
# fields
class Student(Person):
# fields
teacher = models.ForeignKey(teacher)
class Staff(Person):
# fields
class SomeModel(models.Model):
# fields
point_person = models.ForeignKey(Person)
But I want to limit my "point_person" to Teacher and Student only. How can I do this?
I would offer to implement a custom manager, probably overriding the get_queryset.
I can see 2 solutions to get only those Parent, who have ChildA and/or ChildB.
If one Parent never has ChildA and ChildB same time (or any other combination), you can add an extra field (db column) to Parent, which indicates what is the class of its child object, if any. In get_queryset of your custom manager you always check this field.
If one Parent can have multiple classes of Child simultaneously, of if you don't want to add an extra column, then you override get_queryset, to actually select from ChildA and ChildB, and combine the querysets afterwards into a single queryset.
I want to get a derived modelclass instance from an instanciated base modelclass.
I have the following model class hierarchy:
class AlfrescoPFCModel(models.Model):
#some fields and methods
class Meta:
abstract = True
class Contenido(AlfrescoPFCModel):
#some fields and methods
class Proyecto(Contenido):
#some fields and methods
class ProyectoCalificado(Proyecto):
#some fields and methods
class ProyectoArchivado(ProyectoCalificado):
#some fields and methods
And I instantiate a Proyecto class in this way:
proyecto = proyecto_form.save(commit=False)
#do some stuff with some fields that dont appear on the form
proyecto.save
In another view I try to access the derived class ProyectoCalificado from the base class Proyecto previously instanciated and saved in the database doing:
pc = ProyectoCalificado.objects.get(pk=id)
and i get: ProyectoCalificado matching query does not exist.
I also tried:
p = get_object_or_404(Proyecto, id=id)
pc = p.proyectocalificado
but it get the same error in the second line.
Reading the documentation i should be allowed to do that:
#multi-table-inheritance
What i want to do is to incrementally complete the data associated to a Proyecto (project) following this workflow: Proyecto -> ProyectoCalificado -> ProyectoArchivado.
I have 3 different forms for each step. I need 3 different models because I need to save them in the database without filling all the mandatory fields at once.
Thanks!
Use Form Wizards (https://docs.djangoproject.com/en/dev/ref/contrib/formtools/form-wizard/).
UPDATE
If you can't use Form Wizards because of the situation you describe, then you should make the models fields blank or nullable at the database level and then only enforce field requirements on each individual form. Creating three levels of inheritance solely for the purpose of the single-time set of forms required to create it is absolutely the wrong approach. It only fragments your data across additional tables and makes querying more complicated with no long-term benefit.
So, for example. Set the model itself up as though nothing (or only the items in the first form) are required. Then, in your first form, only make the fields necessary for that particular stage required. You can do this easily by overriding the __init__:
class FirstForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FirstForm, self).__init__(*args, **kwargs)
self.fields['some_required_field'].required = True
# rinse and repeat
Do the same in your second and third forms, again making on the actual fields that are required for that particular form, required.
Then, call it day and have a drink.
If you have the child instance, it should have a <base class name>_ptr member which points to the instance of its superclass. You can use this as the basis of a filter query to retrieve the child.
You can also just assume that instances of the base and derived class will have the same id if you haven't done anything to affect how ids are allocated.