django has more than 1 foreignkey error - python

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

Related

Parametrize a Python class

Is there any way to parametrize a class in Python? The parametrized class may look something like this:
class FIELDSerializer:
FIELD = serializers.CharField(source='get_FIELD_display', required=False)
class Meta:
model = None
fields = {FIELD}
Which would need to create the following three classes:
class NameSerializer:
name = serializers.CharField(source='get_name_display', required=False)
class Meta:
model = None
fields = {'name'}
class CategorySerializer:
category = serializers.CharField(source='get_category_display', required=False)
class Meta:
model = None
fields = {'category'}
class StateSerializer:
state = serializers.CharField(source='get_state_display', required=False)
class Meta:
model = None
fields = {'state'}
Is this possible or not?
You can do what you want with a factory function, although it's not completely trivial to get the internal variables (attributes) as you want them:
def factory(FIELDname):
class FIELDSerializer:
class Meta:
model = None
fields = {FIELDname}
settatr(FIELDSerializer, FIELDname, serializers.CharField(source=f'get_{FIELDname}_display', required=False))
return FIELDSerializer
CategorySerializer = factory('category')
StateSerializer = factory('state')
NameSerializer = factory('name')
The setattr allows us to set the name of attribute to the FIELDname string. (Thanks to #Code-Apprentice and #juanpa.arrivillaga for this idea.)
I don't know if there's any easy way to avoid the repetition of the field name and the desired class name when you call the factory without using something like exec (which is perfectly legal but usually leaves programmers with a bad taste in their mouths).

Update field in model for all "submodels" changes

I know how to update some field of ForeignKey. For example when I want change last_modified field every time if Configuration or SomeOtherImportantClass is changed:
class Configuration(models.Model):
title = models.TextField()
last_modified = models.DateTimeField(auto_now=True)
class SomeOtherImportantClass(models.Model):
conf = models.ForeignKey(Configuration)
important_number = models.IntegerField()
def save(self, *args, **kwargs):
conf.last_modified = timezone.now() # I'm not sure if it is necessary
conf.save()
return super().save(*args, **kwargs)
but in my real situation the Cofiguration model is a ForeignKey for more than 30 other models. In each of them I want to update configuration.last_modified field for every change performed on them or when another model (which has ForeignKey to some model which has ForeignKey do Configuration) is changed. So it looks like that:
class Configuration(models.Model):
title = models.TextField()
last_modified = models.DateTimeField(auto_now=True)
class A(models.Model):
conf = models.ForeignKey(Configuration) # conf.last_modified must be updated on every change on A model object.
class B(models.Model):
conf = models.ForeignKey(Configuration) # same
...
class Z(models.Model):
conf = models.ForeignKey(Configuration) # same
class AA(models.Model):
some_field = models.TextField()
a = models.ForeignKey(A)
...
class ZZ(models.Model)
some_field = models.TextField()
z = models.ForeignKey(Z)
so even if AA object field "some_field" is changed I want to update last_modified Configuration field. Is there any recursion way to declare it once in Configuration or somewhere else?
UPDATE: Great-granchilds like AAA and AAAA classes can exist too.
Use abstract base classes as explained in the docs. For A-Z it's quite easy:
class ConfigurationChild(Model):
conf = ForeignKey(Configuration)
class Meta:
abstract = True
def save(self):
self.conf.last_modified = ...
self.conf.save()
super().save()
class A(ConfigurationChild):
# other fields, without conf
For the grand-children it's a bit more complex because then don't have a reference to conf directly. Set an attribute on the base class that you populate on each child class:
class ConfigurationDescendant(Model):
conf_handle = None
class Meta:
abstract = True
def get_conf(self):
if not self.conf_handle:
return None # or raise an error
parent = getattr(self, self.conf_handle)
if isinstance(parent, ConfigurationDescendant):
return parent.get_conf() # recursion
else:
# reached `ConfigurationChild` class, might want to check this
return parent.conf if parent else None
def save(self):
conf = self.get_conf()
# you might want to handle the case that the attribute is None
if conf:
conf.last_modified = ...
conf.save()
super().save()
class AA(ConfigurationDescendant):
conf_handle = 'a'
a = ForeignKey(A)
class AAA(ConfigurationDescendant):
conf_handle = 'aa'
aa = ForeignKey(AA)
The above code will handle the case when the chain breaks because conf_handle is missing on one of the parents. In which case None is returned and nothing happens. I'm not checking if the handle is set wrongly (i.e. not pointing in the right direction towards the parent Configuration), that will raise an exception which you probably want so you can catch mistakes.

Can Django contribute_to_class method be used with abstract models?

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?

django rest framework abstract class serializer

I have some models like these:
class TypeBase(models.Model):
name = models.CharField(max_length=20)
class Meta:
abstract=True
class PersonType(TypeBase):
pass
class CompanyType(TypeBase):
pass
Having this, I want to create just one serializer that holds all these field types (serialization, deserialization, update and save).
To be more specific, I want only one serializer (TypeBaseSerializer) that print the Dropdown on the UI, serialize the json response, deserialize it on post and save it for all my based types.
Something like this:
class TypeBaseSerializer(serializers.Serializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
Is it possible?
I think the following approach is more cleaner. You can set "abstract" field to true for the base serializer and add your common logic for all child serializers.
class TypeBaseSerializer(serializers.ModelSerializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
abstract = True
def func(...):
# ... some logic
And then create child serializers and use them for data manipulation.
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = ('id', 'name')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = ('id', 'name')
Now you can use the both of these serializers normally for every model.
But if you really want to have one serializers for both the models, then create a container model and a serializer for him too. That is much cleaner :)
You can't use a ModelSerializer with an abstract base model.
From restframework.serializers:
if model_meta.is_abstract_model(self.Meta.model):
raise ValueError(
'Cannot use ModelSerializer with Abstract Models.'
)
I wrote a serializer_factory function for a similar problem:
from collections import OrderedDict
from restframework.serializers import ModelSerializer
def serializer_factory(mdl, fields=None, **kwargss):
""" Generalized serializer factory to increase DRYness of code.
:param mdl: The model class that should be instanciated
:param fields: the fields that should be exclusively present on the serializer
:param kwargss: optional additional field specifications
:return: An awesome serializer
"""
def _get_declared_fields(attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1]._creation_counter)
return OrderedDict(fields)
# Create an object that will look like a base serializer
class Base(object):
pass
Base._declared_fields = _get_declared_fields(kwargss)
class MySerializer(Base, ModelSerializer):
class Meta:
model = mdl
if fields:
setattr(Meta, "fields", fields)
return MySerializer
You can then use the factory to produce serializers as needed:
def typebase_serializer_factory(mdl):
myserializer = serializer_factory(
mdl,fields=["id","name"],
#owner=HiddenField(default=CurrentUserDefault()),#Optional additional configuration for subclasses
)
return myserializer
Now instanciate different subclass serializers:
persontypeserializer = typebase_serializer_factory(PersonType)
companytypeserializer = typebase_serializer_factory(CompanyType)
As already mentioned in Sebastian Wozny's answer, you can't use a ModelSerializer with an abstract base model.
Also, there is nothing such as an abstract Serializer, as some other answers have suggested. So setting abstract = True on the Meta class of a serializer will not work.
However you need not use use a ModelSerializer as your base/parent serializer. You can use a Serializer and then take advantage of Django's multiple inheritance. Here is how it works:
class TypeBaseSerializer(serializers.Serializer):
# Need to re-declare fields since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
class Meta:
fields = ['id', 'name']
def someFunction(self):
#... will be available on child classes ...
pass
class PersonTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ['another_field']
class CompanyTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields + ['some_other_field']
So now since the fields name and id are declared on the parent class (TypeBaseSerializer), they will be available on PersonTypeSerializer and since this is a child class of ModelSerializer those fields will be populated from the model instance.
You can also use SerializerMethodField on the TypeBaseSerializer, even though it is not a ModelSerializer.
class TypeBaseSerializer(serializers.Serializer):
# you will have to re-declare fields here since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
other_field = serializers.SerializerMethodField()
class Meta:
fields = ['id', 'name', 'other_field']
def get_other_field(self, instance):
# will be available on child classes, which are children of ModelSerializers
return instance.other_field
Just iterating a bit over #adki's answer:
it is possible to skip model for TypeBaseSerializer;
derived serializers can refer to TypeBaseSerializer.Meta, so you would change them in a single place.
class TypeBaseSerializer(serializers.Serializer):
class Meta:
fields = ('id', 'name', 'created')
abstract = True
def func(...):
# ... some logic
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ('age', 'date_of_birth')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields

Inherit and modify a `Meta` class

So I have a base ItemTable, and then a number of Tables that inherit from it. I don't seem to be able to modify the Meta class. I tried just including the meta class normally and it didn't work, then I found this bug report and implemented it below. It fails silently: the tables render only with the columns from the parent meta class.
class ItemTable(tables.Table):
class Meta:
model = Item
attrs = {"class":"paleblue"}
fields = ('name', 'primary_tech', 'primary_biz', 'backup_tech', 'backup_biz')
class ApplicationTable(ItemTable):
def __init__(self, *args, **kwargs):
super(ApplicationTable, self).__init__(*args, **kwargs)
class Meta(ItemTable.Meta):
model = Application
fields += ('jira_bucket_name',)
EDIT: Code amended as shown. I now get a NameError that fields is not defined.
Try:
class ApplicationTable(ItemTable):
class Meta:
model = Application
fields = ItemTable.Meta.fields + ('jira_bucket_name',)
You'll have the same problems extending Meta in a table, as you will in a normal Django model.
You didnt add , (comma) to one-element tuple. Try to change this line Meta.attrs['fields'] += ('jira_bucket_name') in ApplicationTable to:
Meta.attrs['fields'] += ('jira_bucket_name',)
if it didnt help try to create Meta class outsite model class definition:
class ItemTableMeta:
model = Item
attrs = {"class":"paleblue"}
fields = ('name', 'primary_tech', 'primary_biz', 'backup_tech', 'backup_biz')
class ApplicationTableMeta(ItemTableMeta):
model = Application
fields = ItemTableMeta.fields + ('jira_bucket_name',)
class ItemTable(tables.Table):
#...
Meta = ItemTableMeta
class ApplicationTable(ItemTable):
#...
Meta = ApplicationTableMeta
You may need to take this up with the django-tables author. This is not a problem with standard Django.

Categories