Here is my problem: I try to create layer under
models.Model
My Model -
class MainModel(models.Model):
#staticmethod
def getIf(condition):
results = __class__.objects.filter(condition)
if results.count() > 0:
return results.first()
else:
return None
And that's a model
class User(MainModel):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=256)
date_create = models.DateTimeField(auto_now_add=True)
date_last_login = models.DateTimeField(null=True)
But my project is crushed with error -
django.core.exceptions.FieldError: Local field 'id' in class 'User'
clashes with field of the same name from base class 'MainModel'.
What am I doing wrong?
UPD: if you want to do like this, you need to use subclass Meta in your layer
class MainModel(models.Model):
#staticmethod
def getIf(condition:dict):
results = __class__.objects.filter(condition)
if results.count() > 0:
return results.first()
else:
return None
class Meta:
abstract = True
Thanx, but I'm not trying to override fields, In my layer no one field is not defined. I found my answer, I just have to read documentation.
if you want to do like this, you need to use subclass Meta in your layer
class MainModel(models.Model):
#staticmethod
def getIf(condition:dict):
results = __class__.objects.filter(condition)
if results.count() > 0:
return results.first()
else:
return None
class Meta:
abstract = True
Django adds a field id to all Models, you have to remove it.
Ok I understand your question better now, your answer is there:
In Django - Model Inheritance - Does it allow you to override a parent model's attribute?
Django already adds a field id to your parent model.
Related
I am facing the following scenario;
#models.py
class A(models.Model):
dog = models.CharField(...)
cat_values = models.ManyToManyField(B, through='AtoB')
class AtoB(models.Model):
one = models.ForeignKey(A)
two = models.ForeignKey(B)
class B(models.Model):
cat = models.CharField(...)
#admin.py
class BForm(forms.ModelForm):
cat = forms.TextInput()
class Meta:
model = A.cat_values.through
exclude = []
class BInline(admin.TabularInline):
model = B.cat_values.through
form = BForm
fields = ['cat', ]
readonly_fields = ['cat', ]
#admin.register(A)
class AAdmin(admin.ModelAdmin):
inlines = [BInline,]
When I try to run server, I get the following error message;
<class 'agregator.admin.AAdmin.BInline'>: (admin.E035) The value of 'readonly_fields[0]' is not a callable, an attribute of 'BInline', or an attribute of 'agregator.AtoB'.
No other forms of access really work like A.cat_values or A.cat_values.two
I partially understand where is the problem coming from. It does not let me access the B's attributes, only AtoB's attributes. I have tried to work around it but unsuccessfully. Have read the documentation but there is nothing about accessing the attributes in this case scenario, only without a defined through model.
eg https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_many/ or https://docs.djangoproject.com/en/3.1/topics/db/models/
I need to display the cat attribute in the inline in the A's admin. Any help would be much appreciated.
So I have solved the issue. The key was to define properties for the AtoB model.
For example;
class AtoB(models.Model):
one = models.ForeignKey(A)
two = models.ForeignKey(B)
#property
def cat(self):
return self.two.cat
I have created a nested serializer, when I try to post data in it it keeps on displaying either the foreign key value cannot be null or dictionary expected. I have gone through various similar questions and tried the responses but it is not working for me. Here are the models
##CLasses
class Classes(models.Model):
class_name = models.CharField(max_length=255)
class_code = models.CharField(max_length=255)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.class_name
class Meta:
ordering = ['class_code']
##Streams
class Stream(models.Model):
stream_name = models.CharField(max_length=255)
classes = models.ForeignKey(Classes,related_name="classes",on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.stream_name
class Meta:
ordering = ['stream_name']
Here is the view
class StreamViewset(viewsets.ModelViewSet):
queryset = Stream.objects.all()
serializer_class = StreamSerializer
Here is the serializer class
class StreamSerializer(serializers.ModelSerializer):
# classesDetails = serializers.SerializerMethodField()
classes = ClassSerializer()
class Meta:
model = Stream
fields = '__all__'
def create(self,validated_data):
classes = Classes.objects.get(id=validated_data["classes"])
return Stream.objects.create(**validated_data, classes=classes)
# def perfom_create(self,serializer):
# serializer.save(classes=self.request.classes)
#depth = 1
# def get_classesDetails(self, obj):
# clas = Classes.objects.get(id=obj.classes)
# classesDetails = ClassSerializer(clas).data
# return classesDetails
I have tried several ways of enabling the create method but like this displays an error {"classes":{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}}. Any contribution would be deeply appreciated
This is a very common situation when developing APIs with DRF.
The problem
Before DRF reaches the create() method, it validates the input, which I assume has a form similar to
{
"classes": 3,
"stream_name": "example"
}
This means that, since it was specified that
classes = ClassSerializer()
DRF is trying to build the classes dictionary from the integer. Of course, this will fail, and you can see that from the error dictionary
{"classes":{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}}
Solution 1 (requires a new writable field {field_name}_id)
A possible solution is to set read_only=True in your ClassSerializer, and use an alternative name for the field when writing, it's common to use {field_name}_id. That way, the validation won't be done. See this answer for more details.
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
'classes_id',
)
extra_kwargs = {
'classes_id': {'source': 'classes', 'write_only': True},
}
This is a clean solution but requires changing the user API. In case that's not an option, proceed to the next solution.
Solution 2 (requires overriding to_internal_value)
Here we override the to_internal_value method. This is where the nested ClassSerializer is throwing the error. To avoid this, we set that field to read_only and manage the validation and parsing in the method.
Note that since we're not declaring a classes field in the writable representation, the default action of super().to_internal_value is to ignore the value from the dictionary.
from rest_framework.exceptions import ValidationError
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
def to_internal_value(self, data):
classes_pk = data.get('classes')
internal_data = super().to_internal_value(data)
try:
classes = Classes.objects.get(pk=classes_pk)
except Classes.DoesNotExist:
raise ValidationError(
{'classes': ['Invalid classes primary key']},
code='invalid',
)
internal_data['classes'] = classes
return internal_data
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
)
With this solution you can use the same field name for both reading and writing, but the code is a bit messy.
Additional notes
You're using the related_name argument incorrectly, see this question. It's the other way around,
classes = models.ForeignKey(
Classes,
related_name='streams',
on_delete=models.CASCADE,
)
In this case it should be streams.
Kevin Languasco describes the behaviour of the create method quite well and his solutions are valid ones. I would add a variation to solution 1:
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
classes_id = serializers.IntegerField(write_only=True)
def create(self,validated_data):
return Stream.objects.create(**validated_data, classes=classes)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'classes_id',
'created_date',
)
The serializer will work without overriding the create method, but you can still do so if you want to as in your example.
Pass the value classes_id in the body of your POST method, not classes. When deserializing the data, the validation will skip classes and will check classes_id instead.
When serializing the data (when you perform a GET request, for example), classes will be used with your nested dictionary and classes_id will be omitted.
You can also solve this issue in such a way,
Serializer class
# Classes serializer
class ClassesSerializer(ModelSerializer):
class Meta:
model = Classes
fields = '__all__'
# Stream serializer
class StreamSerializer(ModelSerializer):
classes = ClassesSerializer(read_only=True)
class Meta:
model = Stream
fields = '__all__'
View
# Create Stream view
#api_view(['POST'])
def create_stream(request):
classes_id = request.data['classes'] # or however you are sending the id
serializer = StreamSerializer(data=request.data)
if serializer.is_valid():
classes_instance = get_object_or_404(Classes, id=classes_id)
serializer.save(classes=classes_instance)
else:
return Response(serializer.errors)
return Response(serializer.data)
I have a parent and a one-to-one related child model and I would like to render the fields from the child flat in the parent representation (read only). Currently, I have achieved that with a custom to_representation implementation but that seems very involved and I wonder if there is no easier way to achieve this.
It is made more complicated by the fact that my related model is connected via a property.
So here is the concrete example:
By default a related object would be rendered like:
{
parent_name:'Bob',
child:{
name:'Alice'
}
}
This is what I want and currently get with my to_representation:
{
parent_name:'Bob',
child_name:'Alice'
}
My models look like this:
class ChildModel(models.Model):
name = models.CharField(max_length=100, null=True)
class ParentModel(models.Model):
name = models.CharField(max_length=100, null=True)
_child = models.ForeignKey('ChildModel', null=True)
#property
def child(self):
return self._most_recent_status
#name.setter
def child(self, value):
self._child = value
Here are my serializers:
class FlatChildField(serializers.RelatedField):
def to_representation(self, value):
return value.name
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = FlatChildField(source='_child', read_only=True)
class Meta:
model = Parent
fields = ('name', 'child_name')
For a simpler solution to get a flat representation of related models I would be grateful.
For completeness, I would be interested to hear if there is a simpler solution for "normal" related models (i.e. not property model fields as well). I was looking for the equivalent of the django model query syntax of related_model__field, but I cannot find that. Does that exist for django rest framework?
Many thanks
The simplest means would be to use source:
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = serializers.CharField(source='_child.name', read_only=True)
class Meta:
model = Parent
fields = ('name', 'child_name')
You can use SerializerMethodField, it saves you really a lot of work and it's so clean and trivial:
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = serializers.SerializerMethodField('get_child_name')
class Meta:
model = Parent
fields = ('name', 'child_name')
def get_child_name(self, obj):
return obj._child.name
I'm trying to access fields on the through table of my ManyToMany link to serialize into a JSON via Django Rest Frameworks.
My models involved in the many to many are:
class Mage(models.Model):
arcana = models.ManyToManyField('ArcanumAbility', through='CharacterArcanumLink', related_name='mage_by_arcana')
class ArcanumAbility(models.Model):
class Arcana(AutoNumber):
FATE = ()
MIND = ()
SPIRIT = ()
DEATH = ()
FORCES = ()
TIME = ()
SPACE = ()
LIFE = ()
MATTER = ()
PRIME = ()
arcanum = EnumField(Arcana)
class Meta:
verbose_name_plural = "Arcana Abilities"
def __str__(self):
return self.arcanum.label
class CharacterArcanumLink(Trait):
PRIORITY_CHOICES = (
(0, 'Unassigned'), (1, 'Ruling'), (2, 'Common'), (3, 'Inferior')
)
priority = models.PositiveSmallIntegerField(
choices=PRIORITY_CHOICES, default=0)
mage = models.ForeignKey('Mage')
arcana = models.ForeignKey('ArcanumAbility')
class Meta:
unique_together = ('mage', 'arcana')
def __str__(self):
return self.arcana.arcanum.label
Where the Trait mixin provides a current_value
To serialize the above relation into my JSON, I have tried these two patters on my serializer:
class CharacterArcanumLinkSerializer(serializers.ModelSerializer):
class Meta:
model = CharacterArcanumLink
fields = ('current_value', 'arcana')
class MageSerializer(serializers.ModelSerializer):
arcana = CharacterArcanumLinkSerializer()
....
class Meta:
model = Mage
fields = (...., 'arcana', ....)
depth = 1
But that gives me this error:
AttributeError at /mages
'ManyRelatedManager' object has no attribute 'arcana'
Which is from (ultimately):
C:\Python34\lib\site-packages\rest_framework\fields.py in get_attribute
if instance is None:
# Break out early if we get `None` at any point in a nested lookup.
return None
try:
if isinstance(instance, collections.Mapping):
instance = instance[attr]
else:
instance = getattr(instance, attr) ...
except ObjectDoesNotExist:
return None
if is_simple_callable(instance):
instance = instance()
return instance
▼ Local vars
Variable: Value
instance: <django.db.models.fields.related.create_many_related_manager.<locals>.ManyRelatedManager object at 0x0000000004E4D4A8>
attr: 'arcana'
attrs: ['arcana']
(Question: What trick to I need to go from my ManyRelatedManager to it's fields?)
And I've also tried not specifying a special serializer, and just having 'arcana' in my fields, and pull it from my model. That leads to this error:
TypeError at /mages
<Arcana.FATE: 1> is not JSON serializable
Where the 1 is from the PK on the ArcanumAbility not the value on the through table. The issue here is that the Mage class has a M2M field that points to the 'ArcanumAbility' model, so all that DRF tries to do is serialize the Enum on it.
So what method should I use if I want a JSON dictionary of all the relationships from Mage to ArcanumAbility with data from the through table?
Responding to Mark R., I'd like it looks like so:
....
"arcanum": {
"Fate": 2,
"Spirit": 0,
"Mind": 3,
....
}
Hopefully that's a clear enough sample.
As discussed, if you add a related_name="linked_arcana" to the mage field in the CharacterArcanumLink class, you should be able to do something like this:
class MageSerializer(serializers.ModelSerializer):
arcana = serializers.SerializerMethodField()
def get_arcana(self, obj):
if obj:
return {str(x): x.current_value for x in obj.linked_arcana.all()}
I have gotten this working like so:
arcana = serializers.SerializerMethodField()
def get_arcana(self, obj):
if obj:
return {str(link): link.current_value
for link in CharacterArcanumLink.objects.filter(mage=obj)}
Heavily inspired by this answer.
FactoryBoy seem to always create the instances in the default database. But I have the following problem.
cpses = CanonPerson.objects.filter(persons__vpd=6,
persons__country="United States").using("global")
The code is pointing to the global database. I haven't found a way to specify the database within the factory:
class CanonPersonFactory(django_factory.DjangoModelFactory):
class Meta:
model = CanonPerson
django_get_or_create = ('name_first', 'p_id')
p_id = 1
name_first = factory.Sequence(lambda n: "name_first #%s" % n)
#factory.post_generation
def persons(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of groups were passed in, use them
for person in extracted:
self.persons.add(person)
Looks like Factory Boy does not provide this feature from box, but you can easily add it manually:
class CanonPersonFactory(django_factory.DjangoModelFactory):
class Meta:
model = CanonPerson
...
#classmethod
def _get_manager(cls, model_class):
manager = super(CanonPersonFactory, cls)._get_manager(model_class)
return manager.using('global')
...
This is now directly supported by adding the database attribute on Meta:
class CanonPersonFactory(django_factory.DjangoModelFactory):
class Meta:
model = CanonPerson
database = 'global'
...