If I have a Django model:
class Person(models.Model):
name = models.CharField(max_length=45)
surname = models.CharField(max_length=45)
objects = PersonManager()
and its manager:
class PersonManager(models.Manager):
def create_person(self, fullname):
name, surname = fullname.split(" ", 1)
return self.create(name=name, surname=surname)
Extending that class:
class Actor(Person):
pass
doesn't have the same manager object, but the default one.
In [5]: Person.objects.create_person
Out[5]: <bound method PersonManager.create_person of <xxx.PersonManager object at 0x10297d190>>
In [6]: Actor.objects.create_person
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-6-468f84e48664> in <module>()
----> 1 Actor.objects.create_person
AttributeError: 'Manager' object has no attribute 'create_person'
Why is that and how can I propagate the manager to all subclasses?
You can read more about this here: https://docs.djangoproject.com/en/dev/topics/db/managers/#custom-managers-and-model-inheritance
Basically managers are not inherited (from ordinary models). To get around this you can manually declare the manager in each model that should use the PersonManager. Or you can make Person an abstract model, that way all subclasses will inherit the PersonManager. Considering that you might want to add Directors, Producers or Technicians this might be the way to go. To do that you add the abstract attribute to Persons metaclass.
class Person(models.Model):
name = models.CharField(max_length=45)
surname = models.CharField(max_length=45)
objects = PersonManager()
class Meta:
abstract = True
Related
I have two models called car and producer. The two models have many to one relation between them.
class Producer(models.Model):
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=20)
producer = models.ForeignKey(Producer, on_delete=models.CASCADE)
def __str__(self):
return self.name here
When i try to query the reverse relationship.
Producer.objects.filter(car__name='Mini')
then it return a queryset object
<QuerySet [<Producer: BMW>]>
when i try to assign the queryset object to a variable and fetch the name field result then it gives error.
obj1 = Producer.objects.filter(car__name='Mini')
In [6]: obj1.name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-6-5155cb2773b4> in <module>
----> 1 obj1.name
AttributeError: 'QuerySet' object has no attribute 'name'
what could be the reason for this error
When do you use get() Django return an object and you can get the variables of that object, for example obj1.name, but when you use filter, Django return a Queryset, you have to iterate the queryset with a for:
mini_producers = Producer.objects.filter(car__name='Mini')
for producer in mini_producers:
print(producer.name)
Queryset is a list of objects, not a single object.
So you can do:
obj1 = Producer.objects.filter(car__name='Mini').first(). # <- get first
In [6]: obj1.name
or in case you have to handle multiple.
for obj in obj1:
print(obj.name)
# do your logic
Is there a way to use contribute_to_class method with abstract models, such that it would be executed separately for each child?
I have the following models:
class BaseHistoryModel(Model):
change = JSONField(blank=True, verbose_name=_('Change'))
comment = models.TextField(blank=True)
class Meta:
abstract = True
class HistoryHelper:
def contribute_to_class(self, cls, *_args):
self.create_history_model(cls)
def create_history_model(self, cls):
attrs = {'__module__': cls.__module__}
history_model = type(
'{}{}'.format(sender.__name__, 'History'),
(BaseHistoryModel, ), attrs
)
history_model.target = models.ForeignKey(cls)
return history_model
class BaseModel(models.Model):
name = models.CharField()
history = HistoryHelper()
class Meta:
abstract = True
I need it to create a separate history model for every child of BaseModel, but when I run makemigrations all I get is one change which creates history model for the abstract BaseModel. Is there a way to somehow get it to create one for every child?
Suppose I have 3 Django models:
class MyModelA(models.Model):
my_int = models.IntegerField()
#staticmethod
def my_Astatic_method():
return "AHello"
class MyModelB(models.Model):
my_int = models.IntegerField()
my_a = models.ForeignKey(MyModelA, related_name="MyModelB_a")
#staticmethod
def my_Bstatic_method():
return "BHello"
class MyModelC(models.Model):
my_int = models.IntegerField()
my_b = models.ForeignKey(MyModelB, related_name="MyModelC_b")
#staticmethod
def my_Cstatic_method():
return "CHello"
I have an instance of MyModelA called a. From within a method of a, I would like to call my_Cstatic_method(). How can I do it?
a.MyModelB_a.model.my_Bstatic_method() works to call MyModelB's static method. But I don't know how to get one level down into MyModelC's static methods from MyModelB's model attribute. How should I do it?
Surprisingly, there is no model attribute for the object a.MyModelB_a.model.MyModelC_b
After accessing model B from model A, you don't have model instance anymore, but model class.
With class, there is no fields but rather field descriptors, also for related fields, so there shoudl be MyModelC_b here:
a.MyModelB_a.model.MyModelC_b
but, as I said this isn't a field, but it have reference to related model as an sub-attribute in attribute related:
a.MyModelB_a.model.MyModelC_b.related.model
using that, you can now accss your static function from model C
a.MyModelB_a.model.MyModelC_b.related.model.my_Cstatic_method()
Let's say I have 3 models in Django: Person, Workfield and SubWorkfield.
A person can have many workfield-s and many subWorkfield-s as well, but the subWorkfield-s must be related to their parent workfield-s ( which the person must be related to).
So how do I enforce that whenever a person is related to a subWorkfield then he must also be related to that subWorkfield's parent workfield?
Here's what I have so far, but I don't think it enforces the relationship:
class Person(models.Model):
name = models.CharField(max_length=200)
workfield = models.ManyToManyField(Workfield)
subworkfield = models.ManyToManyField(SubWorkfield)
class Workfield(models.Model):
name = models.CharField(max_length=200)
class SubWorkfield(models.Model):
name = models.CharField(max_length=200)
workfield = models.ForeignKey(Workfield)
I need to have the workfields and subWorkfield-s decoupled because a person can belong to a workfield without any subWorkfield-s.
There really isn't any good way to do what you want purely with the table structure itself. However, if you do the following:
class Person(models.Model):
name = models.CharField(max_length=200)
workfields = models.ManyToManyField(Workfield)
subworkfields = models.ManyToManyField(SubWorkfield)
def add_subworkfield(self, subworkfield):
if subworkfield.workfield not in self.workfields:
return False
else:
self.subworkfields.append(subworkfield)
class Workfield(models.Model):
name = models.CharField(max_length=200)
class SubWorkfield(models.Model):
name = models.CharField(max_length=200)
workfield = models.ForeignKey(Workfield)
And then you will just use the add_subworkfield method when adding subworkfields to Persons.
Alternatively, inside of the Person model you could override the save method:
class Person(models.Model):
...
def save(self, *args, **kwargs):
for subworkfield in self.subworkfields:
if subworkfield.workfield not in self.workfields:
return
super(Person, self).save(*args, **kwargs)
If it's a small app and it's just you, the first way should suffice. However, if you're working on something larger with multiple people, overriding save would be a safety incase someone doesn't use the add_subworkfield method.
My models file works just fine. As soon as I replace every models.Model with MyModel (a child-class of models.Model), one of my models raises a
<class 'puppy.cms.models.Appearance'> has more than 1 ForeignKey to <class 'puppy.cms.models.Segment'>
exception. The only thing that I am doing in the child class is override the clean method.
What could I be doing wrong?
class SansHashUrl(object):
""" Upon each call to clean, iterates over every field,
and deletes all '#/' and '#!/' occurances.
IMPORTANT: This mixin must be listed first in the inheritance list to work
properly. """
def clean(self):
attrs = (field.attname for field in self.__class__._meta.fields
if isinstance(field, models.CharField)
or isinstance(field, models.TextField))
for attr in attrs:
attr_value = self.__getattribute__(attr)
tokens = attr_value.split()
for i, token in enumerate(tokens):
if has_internal_domain(token):
suggested_url = re.sub('#!?/','', token)
tokens[i] = suggested_url
self.__setattr__(attr, ' '.join(tokens))
class MyModel(SansHashUrl, models.Model):
pass
Model that throws the error:
class Appearance(MyModel):
appearance_type = models.CharField(max_length=20,
choices=APPEARANCE_TYPE_CHOICES)
person = models.ForeignKey(Person, related_name='person_appearance')
item = models.ForeignKey(ManagedItem)
class Meta:
unique_together = (('person', 'item'),)
def __unicode__(self):
return self.person.__unicode__()
In reference to:
class Segment(Story, HasStatsTags, HasFullUrl):
...
It might be useful to note that Story is a subclass of ManagedItem (a subclass of MyModel).
You need to declare MyModel (and probably ManagedItem) as an abstract model in its Meta class, otherwise Django will create a separate table for them and define FKs between them.
class MyModel(SansHashUrl, models.Model):
class Meta:
abstract = True